<?php

function e($value)
{
    return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}




function database_config()
{
    static $config = null;

    if ($config === null) {
        $path = __DIR__ . '/../data/database.php';
        $config = is_file($path) ? require $path : array();
        if (!is_array($config)) {
            $config = array();
        }
    }

    return $config;
}

function database_enabled()
{
    $config = database_config();
    $mysql = array_get($config, 'mysql', array());

    return is_array($mysql) && !empty($mysql['enabled']);
}

function db()
{
    static $pdo = false;

    if ($pdo !== false) {
        return $pdo;
    }

    if (!database_enabled() || !class_exists('PDO')) {
        $pdo = null;
        return null;
    }

    $config = database_config();
    $mysql = array_get($config, 'mysql', array());
    $host = array_get($mysql, 'host', 'localhost');
    $database = array_get($mysql, 'database', '');
    $username = array_get($mysql, 'username', '');
    $password = array_get($mysql, 'password', '');
    $charset = array_get($mysql, 'charset', 'utf8mb4');

    if ($database === '' || $username === '') {
        $pdo = null;
        return null;
    }

    $dsn = 'mysql:host=' . $host . ';dbname=' . $database . ';charset=' . $charset;

    try {
        $pdo = new PDO($dsn, $username, $password, array(
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false,
        ));
    } catch (Exception $e) {
        $pdo = null;
    }

    return $pdo;
}

function decode_json_array($value)
{
    $decoded = json_decode((string) $value, true);
    return is_array($decoded) ? $decoded : array();
}

function product_from_db_row($row)
{
    $width = (float) array_get($row, 'width_cm', 0);
    $depth = (float) array_get($row, 'depth_cm', 0);
    $height = (float) array_get($row, 'height_cm', 0);
    $dimensions = trim((string) array_get($row, 'dimensions', ''));

    if ($dimensions === '' && ($width || $depth || $height)) {
        $dimensions = number_format($width, 0) . 'cm W x ' . number_format($depth, 0) . 'cm D x ' . number_format($height, 0) . 'cm H';
    }

    return array(
        'id' => (string) array_get($row, 'id', ''),
        'name' => (string) array_get($row, 'name', ''),
        'tagline' => (string) array_get($row, 'tagline', ''),
        'collection' => (string) array_get($row, 'collection', ''),
        'room' => (string) array_get($row, 'room', ''),
        'price' => (float) array_get($row, 'price', 0),
        'compare_at' => (float) array_get($row, 'compare_at', 0),
        'rating' => (float) array_get($row, 'rating', 0),
        'reviews' => (int) array_get($row, 'reviews', 0),
        'stock_status' => (string) array_get($row, 'stock_status', ''),
        'seats' => (string) array_get($row, 'seats', ''),
        'dimensions' => $dimensions,
        'dimensions_cm' => array('width' => $width, 'depth' => $depth, 'height' => $height),
        'material' => (string) array_get($row, 'material', ''),
        'color' => (string) array_get($row, 'color', ''),
        'lead_time' => (string) array_get($row, 'lead_time', ''),
        'warranty' => (string) array_get($row, 'warranty', ''),
        'image' => (string) array_get($row, 'image', ''),
        'model' => (string) array_get($row, 'model', ''),
        'ar_floor_offset_cm' => (float) array_get($row, 'ar_floor_offset_cm', 0),
        'badges' => decode_json_array(array_get($row, 'badges_json', '[]')),
        'features' => decode_json_array(array_get($row, 'features_json', '[]')),
        'swatches' => decode_json_array(array_get($row, 'swatches_json', '[]')),
    );
}

function products_from_database()
{
    $pdo = db();
    if (!$pdo) {
        return null;
    }

    try {
        $stmt = $pdo->query('SELECT * FROM products WHERE is_active = 1 ORDER BY sort_order ASC, created_at DESC, name ASC');
        $rows = $stmt->fetchAll();
        $products = array();
        foreach ($rows as $row) {
            $products[] = product_from_db_row($row);
        }
        return $products;
    } catch (Exception $e) {
        return null;
    }
}

function store_settings()
{
    static $settings = null;

    if ($settings === null) {
        $path = __DIR__ . '/../data/settings.php';
        $settings = is_file($path) ? require $path : array();
        if (!is_array($settings)) {
            $settings = array();
        }
    }

    return $settings;
}

function chat_channels_configured($chat)
{
    if (!is_array($chat)) {
        return false;
    }

    return trim((string) array_get($chat, 'messenger_url', '')) !== ''
        || trim((string) array_get($chat, 'whatsapp_number', '')) !== ''
        || trim((string) array_get($chat, 'email', '')) !== ''
        || trim((string) array_get($chat, 'phone', '')) !== ''
        || array_get($chat, 'show_inquiry_link', false);
}

function products()
{
    static $products = null;

    if ($products === null) {
        $products = products_from_database();

        if ($products === null) {
            $fileProducts = require __DIR__ . '/../data/products.php';
            $products = is_array($fileProducts) ? $fileProducts : array();
        }
    }

    return $products;
}

function array_get($array, $key, $default = '')
{
    return (is_array($array) && isset($array[$key])) ? $array[$key] : $default;
}

function find_product($id)
{
    foreach (products() as $product) {
        if ((string) array_get($product, 'id', '') === (string) $id) {
            return $product;
        }
    }

    return null;
}

function related_products($currentId, $limit = 3)
{
    $current = find_product($currentId);
    $items = array_values(array_filter(products(), function ($product) use ($currentId) {
        return (string) array_get($product, 'id', '') !== (string) $currentId;
    }));

    if (!$current) {
        return array_slice($items, 0, $limit);
    }

    usort($items, function ($a, $b) use ($current) {
        $aScore = ((string) array_get($a, 'collection', '') === (string) array_get($current, 'collection', '')) ? 0 : 1;
        $bScore = ((string) array_get($b, 'collection', '') === (string) array_get($current, 'collection', '')) ? 0 : 1;

        if ($aScore === $bScore) {
            return 0;
        }

        return ($aScore < $bScore) ? -1 : 1;
    });

    return array_slice($items, 0, $limit);
}

function collections()
{
    $collections = array();

    foreach (products() as $product) {
        $collection = trim((string) array_get($product, 'collection', ''));
        if ($collection !== '') {
            $collections[$collection] = $collection;
        }
    }

    natcasesort($collections);

    return array_values($collections);
}

function money($amount)
{
    return '&#8369;' . number_format((float) $amount, 2);
}

function active_nav($page, $current)
{
    return $page === $current ? 'active' : '';
}

function image_webp($imagePath)
{
    $webpPath = preg_replace('/\.(png|jpg|jpeg)$/i', '.webp', $imagePath);

    if (!$webpPath) {
        return null;
    }

    $fullPath = __DIR__ . '/../' . ltrim($webpPath, '/');

    return is_file($fullPath) ? $webpPath : null;
}

function product_image($product, $class = 'product-image', $lazy = true)
{
    $src = (string) array_get($product, 'image', '');
    $alt = (string) array_get($product, 'name', 'Product image');
    $webp = image_webp($src);
    $loading = $lazy ? ' loading="lazy"' : ' loading="eager" fetchpriority="high"';

    $html = '<picture>';
    if ($webp) {
        $html .= '<source srcset="' . e($webp) . '" type="image/webp">';
    }
    $html .= '<img src="' . e($src) . '" alt="' . e($alt) . '" class="' . e($class) . '"' . $loading . '>';
    $html .= '</picture>';

    return $html;
}

function start_secure_session()
{
    if (session_status() === PHP_SESSION_ACTIVE) {
        return;
    }

    $isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');

    if (PHP_VERSION_ID >= 70300) {
        session_set_cookie_params(array(
            'lifetime' => 0,
            'path' => '/',
            'secure' => $isHttps,
            'httponly' => true,
            'samesite' => 'Lax',
        ));
    } else {
        session_set_cookie_params(0, '/; samesite=Lax', '', $isHttps, true);
    }

    session_start();
}

function random_token_bytes($length)
{
    if (function_exists('random_bytes')) {
        return random_bytes($length);
    }

    if (function_exists('openssl_random_pseudo_bytes')) {
        $bytes = openssl_random_pseudo_bytes($length, $strong);
        if ($bytes !== false && $strong) {
            return $bytes;
        }
    }

    $bytes = '';
    for ($i = 0; $i < $length; $i++) {
        $bytes .= chr(mt_rand(0, 255));
    }

    return $bytes;
}

if (!function_exists('hash_equals')) {
    function hash_equals($knownString, $userString)
    {
        if (!is_string($knownString) || !is_string($userString)) {
            return false;
        }

        if (strlen($knownString) !== strlen($userString)) {
            return false;
        }

        $result = 0;
        for ($i = 0; $i < strlen($knownString); $i++) {
            $result |= ord($knownString[$i]) ^ ord($userString[$i]);
        }

        return $result === 0;
    }
}

function csrf_token()
{
    start_secure_session();

    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_token_bytes(32));
    }

    return (string) $_SESSION['csrf_token'];
}

function verify_csrf_token($token)
{
    start_secure_session();

    return isset($_SESSION['csrf_token']) && hash_equals((string) $_SESSION['csrf_token'], (string) $token);
}

function normalize_cart_payload($payload)
{
    $decoded = json_decode($payload, true);

    if (!is_array($decoded)) {
        return array();
    }

    $items = array();

    foreach ($decoded as $line) {
        if (!is_array($line)) {
            continue;
        }

        $id = trim((string) array_get($line, 'id', ''));
        $qty = (int) array_get($line, 'qty', 0);

        if ($id === '' || $qty < 1) {
            continue;
        }

        $qty = min($qty, 25);
        $product = find_product($id);

        if (!$product) {
            continue;
        }

        if (isset($items[$id])) {
            $items[$id]['qty'] = min(25, $items[$id]['qty'] + $qty);
            $items[$id]['line_total'] = $items[$id]['qty'] * $items[$id]['price'];
            continue;
        }

        $items[$id] = array(
            'id' => $id,
            'name' => (string) $product['name'],
            'collection' => (string) $product['collection'],
            'price' => (float) $product['price'],
            'qty' => $qty,
            'image' => (string) $product['image'],
            'dimensions' => (string) $product['dimensions'],
            'line_total' => (float) $product['price'] * $qty,
        );
    }

    return array_values($items);
}

function order_total($items)
{
    return array_reduce($items, function ($total, $item) {
        return $total + ((float) array_get($item, 'line_total', 0));
    }, 0.0);
}

function clean_text($value, $maxLength = 180)
{
    $cleaned = preg_replace('/\s+/', ' ', $value);
    if ($cleaned === null) {
        $cleaned = '';
    }

    $cleaned = trim($cleaned);

    return function_exists('mb_substr') ? mb_substr($cleaned, 0, $maxLength) : substr($cleaned, 0, $maxLength);
}


function payment_settings()
{
    $settings = store_settings();
    $payments = array_get($settings, 'payments', array());
    return is_array($payments) ? $payments : array();
}

function payment_method_options()
{
    return array(
        'GCash Manual' => 'GCash manual payment / upload proof',
        'Credit/Debit Card Payment Link' => 'Credit/Debit card via secure payment link',
        'Bank Transfer' => 'Bank transfer / upload proof',
        'Cash on Delivery' => 'Cash on Delivery / pay on delivery',
        'Downpayment Inquiry' => 'Downpayment first, then balance before delivery',
    );
}

function payment_status_options()
{
    return array(
        'Unpaid',
        'Awaiting proof',
        'Pending verification',
        'Payment link sent',
        'Partially paid',
        'Paid',
        'Failed',
        'Refunded',
    );
}

function save_uploaded_payment_proof($file)
{
    if (!isset($file) || !is_array($file) || !isset($file['error']) || (int) $file['error'] === UPLOAD_ERR_NO_FILE) {
        return array('ok' => true, 'path' => '', 'error' => '');
    }

    if ((int) $file['error'] !== UPLOAD_ERR_OK) {
        return array('ok' => false, 'path' => '', 'error' => 'Payment proof upload failed. Please try again.');
    }

    $maxBytes = 6 * 1024 * 1024;
    if ((int) $file['size'] > $maxBytes) {
        return array('ok' => false, 'path' => '', 'error' => 'Payment proof is too large. Maximum file size is 6MB.');
    }

    $ext = strtolower(pathinfo((string) $file['name'], PATHINFO_EXTENSION));
    $allowed = array('jpg', 'jpeg', 'png', 'webp', 'pdf');
    if (!in_array($ext, $allowed, true)) {
        return array('ok' => false, 'path' => '', 'error' => 'Only JPG, PNG, WebP, and PDF payment proofs are allowed.');
    }

    if ($ext !== 'pdf' && function_exists('getimagesize') && @getimagesize($file['tmp_name']) === false) {
        return array('ok' => false, 'path' => '', 'error' => 'Uploaded payment proof is not a valid image.');
    }

    $dir = __DIR__ . '/../storage/payment_proofs';
    if (!is_dir($dir) && !mkdir($dir, 0775, true) && !is_dir($dir)) {
        return array('ok' => false, 'path' => '', 'error' => 'Payment proof folder cannot be created.');
    }

    $filename = 'payment-' . date('Ymd-His') . '-' . strtolower(bin2hex(random_token_bytes(4))) . '.' . $ext;
    $target = $dir . '/' . $filename;

    if (!move_uploaded_file($file['tmp_name'], $target)) {
        return array('ok' => false, 'path' => '', 'error' => 'Payment proof upload failed. Please try again.');
    }

    return array('ok' => true, 'path' => 'storage/payment_proofs/' . $filename, 'error' => '');
}

function initial_payment_status($paymentMethod, $proofPath)
{
    if ($paymentMethod === 'GCash Manual' || $paymentMethod === 'Bank Transfer') {
        return $proofPath !== '' ? 'Pending verification' : 'Awaiting proof';
    }

    if ($paymentMethod === 'Credit/Debit Card Payment Link') {
        return 'Payment link sent';
    }

    return 'Unpaid';
}

function payment_balance($total, $amountPaid)
{
    $balance = (float) $total - (float) $amountPaid;
    return $balance > 0 ? $balance : 0;
}

function generate_order_number()
{
    return 'FU-' . date('Ymd') . '-' . strtoupper(bin2hex(random_token_bytes(3)));
}

function save_order_to_json($order)
{
    $storageDir = __DIR__ . '/../storage/orders';

    if (!is_dir($storageDir) && !mkdir($storageDir, 0775, true) && !is_dir($storageDir)) {
        return false;
    }

    $orderNumber = isset($order['order_number']) ? $order['order_number'] : generate_order_number();
    $fileName = preg_replace('/[^A-Z0-9\-]/', '', (string) $orderNumber);
    if (!$fileName) {
        $fileName = generate_order_number();
    }
    $path = $storageDir . '/' . $fileName . '.json';

    return file_put_contents($path, json_encode($order, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), LOCK_EX) !== false;
}

function save_order_to_mysql($order)
{
    $pdo = db();
    if (!$pdo) {
        return false;
    }

    try {
        $pdo->beginTransaction();

        $customer = isset($order['customer']) && is_array($order['customer']) ? $order['customer'] : array();
        $preferences = isset($order['preferences']) && is_array($order['preferences']) ? $order['preferences'] : array();
        $payment = isset($order['payment']) && is_array($order['payment']) ? $order['payment'] : array();
        $total = (float) array_get($order, 'total', 0);
        $amountPaid = (float) array_get($payment, 'amount_paid', 0);
        $balanceDue = payment_balance($total, $amountPaid);

        $stmt = $pdo->prepare('INSERT INTO orders (customer_id, order_number, customer_name, customer_phone, customer_email, customer_city, customer_address, payment_method, payment_status, payment_reference, payment_proof_path, payment_link, amount_paid, balance_due, delivery_method, message, total, status, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())');
        $stmt->execute(array(
            (int) array_get($order, 'customer_id', 0) ?: null,
            array_get($order, 'order_number', generate_order_number()),
            array_get($customer, 'name', ''),
            array_get($customer, 'phone', ''),
            array_get($customer, 'email', ''),
            array_get($customer, 'city', ''),
            array_get($customer, 'address', ''),
            array_get($preferences, 'payment_method', ''),
            array_get($payment, 'status', 'Unpaid'),
            array_get($payment, 'reference', ''),
            array_get($payment, 'proof_path', ''),
            array_get($payment, 'payment_link', ''),
            $amountPaid,
            $balanceDue,
            array_get($preferences, 'delivery_method', ''),
            array_get($preferences, 'message', ''),
            $total,
            array_get($order, 'status', 'Pending quotation'),
        ));

        $orderId = (int) $pdo->lastInsertId();
        $itemStmt = $pdo->prepare('INSERT INTO order_items (order_id, product_id, name, collection, price, qty, image, dimensions, line_total) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
        $items = isset($order['items']) && is_array($order['items']) ? $order['items'] : array();

        foreach ($items as $item) {
            $itemStmt->execute(array(
                $orderId,
                array_get($item, 'id', ''),
                array_get($item, 'name', ''),
                array_get($item, 'collection', ''),
                (float) array_get($item, 'price', 0),
                (int) array_get($item, 'qty', 0),
                array_get($item, 'image', ''),
                array_get($item, 'dimensions', ''),
                (float) array_get($item, 'line_total', 0),
            ));
        }

        $pdo->commit();
        return true;
    } catch (Exception $e) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }
        return false;
    }
}

function save_order($order)
{
    if (database_enabled() && save_order_to_mysql($order)) {
        return true;
    }

    return save_order_to_json($order);
}


function generate_reup_number()
{
    return 'RU-' . date('Ymd') . '-' . strtoupper(bin2hex(random_token_bytes(3)));
}

function reup_service_options()
{
    return array(
        'reupholstery' => 'Re-upholstery / palit tela or leatherette',
        'foam_replacement' => 'Foam replacement',
        'frame_repair' => 'Frame / spring repair',
        'full_makeover' => 'Full sofa makeover',
    );
}

function reup_condition_options()
{
    return array(
        'light' => 'Light wear',
        'medium' => 'Medium wear',
        'heavy' => 'Heavy damage',
        'major' => 'Major repair needed',
    );
}

function reup_issue_options()
{
    return array(
        'stains' => 'Stains / discoloration',
        'torn_fabric' => 'Torn fabric',
        'sagging_foam' => 'Sagging foam',
        'broken_frame' => 'Broken frame',
        'loose_spring' => 'Loose spring',
        'pet_damage' => 'Pet damage',
        'custom_size' => 'Custom size / special shape',
    );
}

function calculate_reup_estimate($serviceType, $condition, $widthCm, $depthCm, $heightCm, $seatCount, $issues)
{
    $widthCm = max(0, (float) $widthCm);
    $depthCm = max(0, (float) $depthCm);
    $heightCm = max(0, (float) $heightCm);
    $seatCount = max(1, min(12, (int) $seatCount));
    $areaM2 = max(1.0, ($widthCm * $depthCm) / 10000);

    $rates = array(
        'reupholstery' => 3600,
        'foam_replacement' => 2400,
        'frame_repair' => 1700,
        'full_makeover' => 5200,
    );

    $minimums = array(
        'reupholstery' => 6500,
        'foam_replacement' => 4500,
        'frame_repair' => 3500,
        'full_makeover' => 9500,
    );

    $rate = isset($rates[$serviceType]) ? $rates[$serviceType] : $rates['reupholstery'];
    $minimum = isset($minimums[$serviceType]) ? $minimums[$serviceType] : $minimums['reupholstery'];
    $base = max($minimum, ($areaM2 * $rate) + ($seatCount * 650));

    $conditionMultiplier = array(
        'light' => 1.00,
        'medium' => 1.18,
        'heavy' => 1.38,
        'major' => 1.65,
    );
    $base *= isset($conditionMultiplier[$condition]) ? $conditionMultiplier[$condition] : 1.18;

    $issueAdd = 0;
    if (is_array($issues)) {
        foreach ($issues as $issue) {
            if ($issue === 'broken_frame') {
                $issueAdd += 1800;
            } elseif ($issue === 'loose_spring') {
                $issueAdd += 1200;
            } elseif ($issue === 'sagging_foam') {
                $issueAdd += 1500;
            } elseif ($issue === 'torn_fabric' || $issue === 'pet_damage') {
                $issueAdd += 900;
            } elseif ($issue === 'custom_size') {
                $issueAdd += 1500;
            } else {
                $issueAdd += 400;
            }
        }
    }

    $base += $issueAdd;
    if ($heightCm > 95) {
        $base *= 1.08;
    }

    $low = round(($base * 0.88) / 100) * 100;
    $high = round(($base * 1.22) / 100) * 100;

    return array(
        'low' => max(0, $low),
        'high' => max($low, $high),
        'area_m2' => round($areaM2, 2),
        'note' => 'Preliminary estimate only. Final price still needs manual checking of the uploaded photo, actual material, and sofa condition.',
    );
}

function save_uploaded_reup_photo($file)
{
    if (!isset($file) || !is_array($file) || !isset($file['error']) || (int) $file['error'] !== UPLOAD_ERR_OK) {
        return array('ok' => false, 'path' => '', 'error' => 'Please upload a clear sofa photo.');
    }

    $maxBytes = 6 * 1024 * 1024;
    if ((int) $file['size'] > $maxBytes) {
        return array('ok' => false, 'path' => '', 'error' => 'Photo is too large. Maximum file size is 6MB.');
    }

    $ext = strtolower(pathinfo((string) $file['name'], PATHINFO_EXTENSION));
    $allowed = array('jpg', 'jpeg', 'png', 'webp');
    if (!in_array($ext, $allowed, true)) {
        return array('ok' => false, 'path' => '', 'error' => 'Only JPG, PNG, and WebP images are allowed.');
    }

    if (function_exists('getimagesize') && @getimagesize($file['tmp_name']) === false) {
        return array('ok' => false, 'path' => '', 'error' => 'Uploaded file is not a valid image.');
    }

    $dir = __DIR__ . '/../assets/images/reup_uploads';
    if (!is_dir($dir) && !mkdir($dir, 0775, true) && !is_dir($dir)) {
        return array('ok' => false, 'path' => '', 'error' => 'Upload folder cannot be created.');
    }

    $filename = 'reup-' . date('Ymd-His') . '-' . strtolower(bin2hex(random_token_bytes(4))) . '.' . $ext;
    $target = $dir . '/' . $filename;

    if (!move_uploaded_file($file['tmp_name'], $target)) {
        return array('ok' => false, 'path' => '', 'error' => 'Upload failed. Please try again.');
    }

    return array('ok' => true, 'path' => 'assets/images/reup_uploads/' . $filename, 'error' => '');
}

function save_reup_to_json($request)
{
    $storageDir = __DIR__ . '/../storage/reup_requests';
    if (!is_dir($storageDir) && !mkdir($storageDir, 0775, true) && !is_dir($storageDir)) {
        return false;
    }

    $number = isset($request['request_number']) ? $request['request_number'] : generate_reup_number();
    $fileName = preg_replace('/[^A-Z0-9\-]/', '', (string) $number);
    if (!$fileName) {
        $fileName = generate_reup_number();
    }

    return file_put_contents($storageDir . '/' . $fileName . '.json', json_encode($request, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), LOCK_EX) !== false;
}

function save_reup_to_mysql($request)
{
    $pdo = db();
    if (!$pdo) {
        return false;
    }

    try {
        $stmt = $pdo->prepare('INSERT INTO reup_requests (customer_id, request_number, customer_name, customer_phone, customer_email, customer_city, customer_address, service_type, condition_level, issue_json, width_cm, depth_cm, height_cm, seat_count, fabric_preference, photo_path, message, estimate_low, estimate_high, status, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())');
        $customer = isset($request['customer']) && is_array($request['customer']) ? $request['customer'] : array();
        $sofa = isset($request['sofa']) && is_array($request['sofa']) ? $request['sofa'] : array();
        $estimate = isset($request['estimate']) && is_array($request['estimate']) ? $request['estimate'] : array();
        $stmt->execute(array(
            (int) array_get($request, 'customer_id', 0) ?: null,
            array_get($request, 'request_number', generate_reup_number()),
            array_get($customer, 'name', ''),
            array_get($customer, 'phone', ''),
            array_get($customer, 'email', ''),
            array_get($customer, 'city', ''),
            array_get($customer, 'address', ''),
            array_get($sofa, 'service_type', ''),
            array_get($sofa, 'condition_level', ''),
            json_encode(array_get($sofa, 'issues', array())),
            (float) array_get($sofa, 'width_cm', 0),
            (float) array_get($sofa, 'depth_cm', 0),
            (float) array_get($sofa, 'height_cm', 0),
            (int) array_get($sofa, 'seat_count', 1),
            array_get($sofa, 'fabric_preference', ''),
            array_get($sofa, 'photo_path', ''),
            array_get($request, 'message', ''),
            (float) array_get($estimate, 'low', 0),
            (float) array_get($estimate, 'high', 0),
            array_get($request, 'status', 'For review'),
        ));
        return true;
    } catch (Exception $e) {
        return false;
    }
}

function save_reup_request($request)
{
    if (database_enabled() && save_reup_to_mysql($request)) {
        return true;
    }

    return save_reup_to_json($request);
}

function generate_custom_quote_number()
{
    return 'CS-' . date('Ymd') . '-' . strtoupper(bin2hex(random_token_bytes(3)));
}

function custom_sofa_type_options()
{
    return array(
        'loveseat' => 'Loveseat / 2-seater',
        'three_seater' => 'Standard 3-seater',
        'l_shape' => 'L-shape sofa',
        'sectional' => 'Sectional sofa',
        'modular' => 'Modular sofa set',
        'sofa_bed' => 'Sofa bed',
        'accent_chair' => 'Accent chair / single seater',
    );
}

function custom_style_options()
{
    return array(
        'modern_minimal' => 'Modern minimal',
        'japandi' => 'Japandi / warm neutral',
        'classic' => 'Classic / elegant',
        'luxury' => 'Luxury hotel style',
        'condo_space_saver' => 'Condo space-saver',
        'family_lounge' => 'Family lounge / extra comfy',
    );
}

function custom_material_options()
{
    return array(
        'standard_fabric' => 'Standard fabric',
        'premium_fabric' => 'Premium performance fabric',
        'linen_blend' => 'Linen blend',
        'leatherette' => 'Leatherette',
        'genuine_leather' => 'Genuine leather',
        'not_sure' => 'Not sure yet / need recommendation',
    );
}

function custom_firmness_options()
{
    return array(
        'soft' => 'Soft',
        'medium_soft' => 'Medium soft',
        'medium_firm' => 'Medium firm',
        'firm' => 'Firm',
        'not_sure' => 'Not sure yet',
    );
}

function custom_feature_options()
{
    return array(
        'chaise' => 'Chaise extension',
        'storage' => 'Built-in storage',
        'sofa_bed' => 'Sofa bed mechanism',
        'recliner' => 'Recliner mechanism',
        'removable_covers' => 'Removable covers',
        'tufting' => 'Tufting / channel stitching',
        'curved_shape' => 'Curved / special shape',
        'wood_accent' => 'Wood accent / legs',
        'ottoman' => 'Matching ottoman',
    );
}

function calculate_custom_sofa_estimate($sofaType, $style, $material, $widthCm, $depthCm, $heightCm, $seatCount, $features)
{
    $widthCm = max(0, (float) $widthCm);
    $depthCm = max(0, (float) $depthCm);
    $heightCm = max(0, (float) $heightCm);
    $seatCount = max(1, min(14, (int) $seatCount));
    $areaM2 = max(0.85, ($widthCm * $depthCm) / 10000);

    $minimums = array(
        'loveseat' => 18500,
        'three_seater' => 24500,
        'l_shape' => 42000,
        'sectional' => 45500,
        'modular' => 52000,
        'sofa_bed' => 38500,
        'accent_chair' => 12500,
    );

    $areaRates = array(
        'loveseat' => 5400,
        'three_seater' => 6000,
        'l_shape' => 7200,
        'sectional' => 7800,
        'modular' => 8400,
        'sofa_bed' => 7600,
        'accent_chair' => 4300,
    );

    $minimum = isset($minimums[$sofaType]) ? $minimums[$sofaType] : $minimums['three_seater'];
    $areaRate = isset($areaRates[$sofaType]) ? $areaRates[$sofaType] : $areaRates['three_seater'];
    $base = max($minimum, $minimum + ($areaM2 * $areaRate) + ($seatCount * 2600));

    $materialMultiplier = array(
        'standard_fabric' => 1.00,
        'premium_fabric' => 1.16,
        'linen_blend' => 1.12,
        'leatherette' => 1.22,
        'genuine_leather' => 1.80,
        'not_sure' => 1.08,
    );
    $base *= isset($materialMultiplier[$material]) ? $materialMultiplier[$material] : 1.08;

    $styleMultiplier = array(
        'modern_minimal' => 1.00,
        'japandi' => 1.06,
        'classic' => 1.12,
        'luxury' => 1.24,
        'condo_space_saver' => 1.04,
        'family_lounge' => 1.10,
    );
    $base *= isset($styleMultiplier[$style]) ? $styleMultiplier[$style] : 1.06;

    $featureAdd = 0;
    if (is_array($features)) {
        foreach ($features as $feature) {
            if ($feature === 'chaise') {
                $featureAdd += 6500;
            } elseif ($feature === 'storage') {
                $featureAdd += 4500;
            } elseif ($feature === 'sofa_bed') {
                $featureAdd += 9500;
            } elseif ($feature === 'recliner') {
                $featureAdd += 12500;
            } elseif ($feature === 'removable_covers') {
                $featureAdd += 3500;
            } elseif ($feature === 'tufting') {
                $featureAdd += 2800;
            } elseif ($feature === 'curved_shape') {
                $featureAdd += 7500;
            } elseif ($feature === 'wood_accent') {
                $featureAdd += 4500;
            } elseif ($feature === 'ottoman') {
                $featureAdd += 5500;
            }
        }
    }

    $base += $featureAdd;
    if ($heightCm > 98) {
        $base *= 1.06;
    }

    $low = round(($base * 0.90) / 100) * 100;
    $high = round(($base * 1.20) / 100) * 100;

    return array(
        'low' => max(0, $low),
        'high' => max($low, $high),
        'area_m2' => round($areaM2, 2),
        'note' => 'Preliminary custom sofa estimate only. Final quote depends on approved design, materials, foam, mechanism, and production details.',
    );
}

function save_uploaded_custom_photo($file, $prefix, $required)
{
    if (!isset($file) || !is_array($file) || !isset($file['error']) || (int) $file['error'] === UPLOAD_ERR_NO_FILE) {
        if ($required) {
            return array('ok' => false, 'path' => '', 'error' => 'Please upload a clear reference photo.');
        }
        return array('ok' => true, 'path' => '', 'error' => '');
    }

    if ((int) $file['error'] !== UPLOAD_ERR_OK) {
        return array('ok' => false, 'path' => '', 'error' => 'Upload failed. Please try again.');
    }

    $maxBytes = 6 * 1024 * 1024;
    if ((int) $file['size'] > $maxBytes) {
        return array('ok' => false, 'path' => '', 'error' => 'Photo is too large. Maximum file size is 6MB.');
    }

    $ext = strtolower(pathinfo((string) $file['name'], PATHINFO_EXTENSION));
    $allowed = array('jpg', 'jpeg', 'png', 'webp');
    if (!in_array($ext, $allowed, true)) {
        return array('ok' => false, 'path' => '', 'error' => 'Only JPG, PNG, and WebP images are allowed.');
    }

    if (function_exists('getimagesize') && @getimagesize($file['tmp_name']) === false) {
        return array('ok' => false, 'path' => '', 'error' => 'Uploaded file is not a valid image.');
    }

    $dir = __DIR__ . '/../assets/images/custom_uploads';
    if (!is_dir($dir) && !mkdir($dir, 0775, true) && !is_dir($dir)) {
        return array('ok' => false, 'path' => '', 'error' => 'Upload folder cannot be created.');
    }

    $safePrefix = preg_replace('/[^a-z0-9\-]/', '', strtolower((string) $prefix));
    if (!$safePrefix) {
        $safePrefix = 'custom';
    }
    $filename = $safePrefix . '-' . date('Ymd-His') . '-' . strtolower(bin2hex(random_token_bytes(4))) . '.' . $ext;
    $target = $dir . '/' . $filename;

    if (!move_uploaded_file($file['tmp_name'], $target)) {
        return array('ok' => false, 'path' => '', 'error' => 'Upload failed. Please try again.');
    }

    return array('ok' => true, 'path' => 'assets/images/custom_uploads/' . $filename, 'error' => '');
}

function save_custom_quote_to_json($request)
{
    $storageDir = __DIR__ . '/../storage/custom_quotes';
    if (!is_dir($storageDir) && !mkdir($storageDir, 0775, true) && !is_dir($storageDir)) {
        return false;
    }

    $number = isset($request['request_number']) ? $request['request_number'] : generate_custom_quote_number();
    $fileName = preg_replace('/[^A-Z0-9\-]/', '', (string) $number);
    if (!$fileName) {
        $fileName = generate_custom_quote_number();
    }

    return file_put_contents($storageDir . '/' . $fileName . '.json', json_encode($request, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), LOCK_EX) !== false;
}

function save_custom_quote_to_mysql($request)
{
    $pdo = db();
    if (!$pdo) {
        return false;
    }

    try {
        $stmt = $pdo->prepare('INSERT INTO custom_sofa_quotes (customer_id, request_number, customer_name, customer_phone, customer_email, customer_city, customer_address, sofa_type, design_style, material_preference, color_preference, firmness, feature_json, width_cm, depth_cm, height_cm, seat_count, chaise_side, budget_range, timeline, inspiration_photo_path, room_photo_path, message, estimate_low, estimate_high, status, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())');
        $customer = isset($request['customer']) && is_array($request['customer']) ? $request['customer'] : array();
        $sofa = isset($request['sofa']) && is_array($request['sofa']) ? $request['sofa'] : array();
        $estimate = isset($request['estimate']) && is_array($request['estimate']) ? $request['estimate'] : array();
        $stmt->execute(array(
            (int) array_get($request, 'customer_id', 0) ?: null,
            array_get($request, 'request_number', generate_custom_quote_number()),
            array_get($customer, 'name', ''),
            array_get($customer, 'phone', ''),
            array_get($customer, 'email', ''),
            array_get($customer, 'city', ''),
            array_get($customer, 'address', ''),
            array_get($sofa, 'sofa_type', ''),
            array_get($sofa, 'design_style', ''),
            array_get($sofa, 'material_preference', ''),
            array_get($sofa, 'color_preference', ''),
            array_get($sofa, 'firmness', ''),
            json_encode(array_get($sofa, 'features', array())),
            (float) array_get($sofa, 'width_cm', 0),
            (float) array_get($sofa, 'depth_cm', 0),
            (float) array_get($sofa, 'height_cm', 0),
            (int) array_get($sofa, 'seat_count', 1),
            array_get($sofa, 'chaise_side', ''),
            array_get($sofa, 'budget_range', ''),
            array_get($sofa, 'timeline', ''),
            array_get($sofa, 'inspiration_photo_path', ''),
            array_get($sofa, 'room_photo_path', ''),
            array_get($request, 'message', ''),
            (float) array_get($estimate, 'low', 0),
            (float) array_get($estimate, 'high', 0),
            array_get($request, 'status', 'For design review'),
        ));
        return true;
    } catch (Exception $e) {
        return false;
    }
}

function save_custom_quote_request($request)
{
    if (database_enabled() && save_custom_quote_to_mysql($request)) {
        return true;
    }

    return save_custom_quote_to_json($request);
}


function site_root_url($path)
{
    $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
    $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost';
    $dir = str_replace('\\', '/', dirname(isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'] : '/'));
    if ($dir === '/' || $dir === '.') {
        $dir = '';
    }
    if (preg_match('#/(admin|account)$#', $dir)) {
        $dir = dirname($dir);
        if ($dir === '/' || $dir === '.') {
            $dir = '';
        }
    }
    return $scheme . '://' . $host . rtrim($dir, '/') . '/' . ltrim($path, '/');
}

function customer_accounts_available()
{
    return db() !== null;
}

function customer_accounts_settings()
{
    $settings = store_settings();
    $accounts = array_get($settings, 'accounts', array());
    return is_array($accounts) ? $accounts : array();
}

function customer_show_debug_verification_link()
{
    $settings = customer_accounts_settings();
    return !empty($settings['show_debug_verification_link']);
}

function customer_find_by_id($id)
{
    $pdo = db();
    if (!$pdo || !$id) {
        return null;
    }
    try {
        $stmt = $pdo->prepare('SELECT * FROM customers WHERE id = ? LIMIT 1');
        $stmt->execute(array((int) $id));
        $row = $stmt->fetch();
        return $row ? $row : null;
    } catch (Exception $e) {
        return null;
    }
}

function customer_find_by_email($email)
{
    $pdo = db();
    if (!$pdo || trim((string) $email) === '') {
        return null;
    }
    try {
        $stmt = $pdo->prepare('SELECT * FROM customers WHERE email = ? LIMIT 1');
        $stmt->execute(array(strtolower(trim((string) $email))));
        $row = $stmt->fetch();
        return $row ? $row : null;
    } catch (Exception $e) {
        return null;
    }
}

function customer_current()
{
    start_secure_session();
    if (empty($_SESSION['customer_id'])) {
        return null;
    }
    $customer = customer_find_by_id((int) $_SESSION['customer_id']);
    if (!$customer || (isset($customer['status']) && $customer['status'] !== 'active')) {
        unset($_SESSION['customer_id']);
        return null;
    }
    return $customer;
}

function customer_is_logged_in()
{
    return customer_current() !== null;
}

function customer_require_login()
{
    if (!customer_is_logged_in()) {
        header('Location: login.php?next=' . rawurlencode(isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : 'customer-dashboard.php'));
        exit;
    }
}

function customer_is_email_verified($customer)
{
    return is_array($customer) && !empty($customer['email_verified_at']);
}

function customer_verification_url($customerId, $token)
{
    return site_root_url('verify-email.php?c=' . urlencode((string) $customerId) . '&t=' . urlencode((string) $token));
}

function customer_generate_email_verification($customerId)
{
    $pdo = db();
    if (!$pdo) {
        return '';
    }
    $token = bin2hex(random_token_bytes(32));
    $hash = password_hash($token, PASSWORD_DEFAULT);
    try {
        $stmt = $pdo->prepare('UPDATE customers SET email_verification_token_hash = ?, email_verification_expires_at = DATE_ADD(NOW(), INTERVAL 24 HOUR), updated_at = NOW() WHERE id = ?');
        $stmt->execute(array($hash, (int) $customerId));
        return $token;
    } catch (Exception $e) {
        return '';
    }
}

function customer_send_email($to, $subject, $body)
{
    $to = trim((string) $to);
    if ($to === '' || !filter_var($to, FILTER_VALIDATE_EMAIL)) {
        return false;
    }
    $headers = array(
        'MIME-Version: 1.0',
        'Content-type: text/plain; charset=UTF-8',
        'From: Furnify <no-reply@' . (isset($_SERVER['HTTP_HOST']) ? preg_replace('/[^a-z0-9\.\-]/i', '', $_SERVER['HTTP_HOST']) : 'localhost') . '>',
    );
    return @mail($to, $subject, $body, implode("\r\n", $headers));
}

function customer_send_verification_email($customer, $token)
{
    if (!is_array($customer) || $token === '') {
        return false;
    }
    $url = customer_verification_url(array_get($customer, 'id', 0), $token);
    $body = "Hi " . array_get($customer, 'full_name', 'Furnify customer') . ",\n\n";
    $body .= "Please verify your Furnify account by opening this link:\n" . $url . "\n\n";
    $body .= "This link expires in 24 hours. If you did not create this account, you can ignore this email.\n\n";
    $body .= "Furnify";
    return customer_send_email(array_get($customer, 'email', ''), 'Verify your Furnify account', $body);
}

function customer_register_account($data)
{
    $pdo = db();
    if (!$pdo) {
        return array('ok' => false, 'error' => 'MySQL is required for customer accounts.', 'customer' => null, 'token' => '', 'mail_sent' => false);
    }

    $name = clean_text(array_get($data, 'full_name', ''), 140);
    $phone = clean_text(array_get($data, 'phone', ''), 40);
    $email = strtolower(clean_text(array_get($data, 'email', ''), 180));
    $city = clean_text(array_get($data, 'city', ''), 140);
    $address = clean_text(array_get($data, 'address', ''), 255);
    $password = (string) array_get($data, 'password', '');

    if ($name === '' || $phone === '' || $email === '' || $password === '') {
        return array('ok' => false, 'error' => 'Please complete all required fields.', 'customer' => null, 'token' => '', 'mail_sent' => false);
    }
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        return array('ok' => false, 'error' => 'Please enter a valid email address.', 'customer' => null, 'token' => '', 'mail_sent' => false);
    }
    if (strlen($password) < 8) {
        return array('ok' => false, 'error' => 'Password must be at least 8 characters.', 'customer' => null, 'token' => '', 'mail_sent' => false);
    }
    if (customer_find_by_email($email)) {
        return array('ok' => false, 'error' => 'This email address is already registered. Please log in instead.', 'customer' => null, 'token' => '', 'mail_sent' => false);
    }

    $passwordHash = password_hash($password, PASSWORD_DEFAULT);
    try {
        $stmt = $pdo->prepare('INSERT INTO customers (full_name, phone, email, password_hash, city, address, status, created_at) VALUES (?, ?, ?, ?, ?, ?, \'active\', NOW())');
        $stmt->execute(array($name, $phone, $email, $passwordHash, $city, $address));
        $id = (int) $pdo->lastInsertId();
        $customer = customer_find_by_id($id);
        $token = customer_generate_email_verification($id);
        $mailSent = customer_send_verification_email($customer, $token);
        return array('ok' => true, 'error' => '', 'customer' => $customer, 'token' => $token, 'mail_sent' => $mailSent);
    } catch (Exception $e) {
        return array('ok' => false, 'error' => 'Account could not be created. Please check database migration.', 'customer' => null, 'token' => '', 'mail_sent' => false);
    }
}

function customer_login($email, $password)
{
    $customer = customer_find_by_email($email);
    if (!$customer || empty($customer['password_hash']) || !password_verify((string) $password, $customer['password_hash'])) {
        return array('ok' => false, 'error' => 'Invalid email or password.');
    }
    if (isset($customer['status']) && $customer['status'] !== 'active') {
        return array('ok' => false, 'error' => 'This account is not active. Please contact Furnify.');
    }
    start_secure_session();
    $_SESSION['customer_id'] = (int) $customer['id'];
    $pdo = db();
    if ($pdo) {
        try {
            $stmt = $pdo->prepare('UPDATE customers SET last_login_at = NOW() WHERE id = ?');
            $stmt->execute(array((int) $customer['id']));
        } catch (Exception $e) {}
    }
    return array('ok' => true, 'error' => '');
}

function customer_logout()
{
    start_secure_session();
    unset($_SESSION['customer_id']);
    unset($_SESSION['last_verification_link']);
}

function customer_verify_email_token($customerId, $token)
{
    $customer = customer_find_by_id((int) $customerId);
    if (!$customer) {
        return array('ok' => false, 'message' => 'Account not found.');
    }
    if (customer_is_email_verified($customer)) {
        return array('ok' => true, 'message' => 'Your email is already verified.');
    }
    $hash = (string) array_get($customer, 'email_verification_token_hash', '');
    $expires = (string) array_get($customer, 'email_verification_expires_at', '');
    if ($hash === '' || $token === '' || !password_verify((string) $token, $hash)) {
        return array('ok' => false, 'message' => 'Invalid verification link. Please request a new one.');
    }
    if ($expires !== '' && strtotime($expires) < time()) {
        return array('ok' => false, 'message' => 'Verification link expired. Please request a new one.');
    }
    $pdo = db();
    try {
        $stmt = $pdo->prepare('UPDATE customers SET email_verified_at = NOW(), email_verification_token_hash = NULL, email_verification_expires_at = NULL, updated_at = NOW() WHERE id = ?');
        $stmt->execute(array((int) $customerId));
        start_secure_session();
        $_SESSION['customer_id'] = (int) $customerId;
        unset($_SESSION['last_verification_link']);
        return array('ok' => true, 'message' => 'Email verified successfully.');
    } catch (Exception $e) {
        return array('ok' => false, 'message' => 'Could not verify email. Please try again.');
    }
}

function customer_resend_verification($customer)
{
    if (!is_array($customer) || customer_is_email_verified($customer)) {
        return array('ok' => false, 'message' => 'Email is already verified.', 'token' => '', 'mail_sent' => false);
    }
    $token = customer_generate_email_verification(array_get($customer, 'id', 0));
    $mailSent = customer_send_verification_email($customer, $token);
    return array('ok' => true, 'message' => $mailSent ? 'Verification email sent.' : 'Verification link generated, but server mail may not be configured.', 'token' => $token, 'mail_sent' => $mailSent);
}

function customer_update_profile($customerId, $data)
{
    $pdo = db();
    if (!$pdo) {
        return array('ok' => false, 'error' => 'MySQL is required.');
    }
    $name = clean_text(array_get($data, 'full_name', ''), 140);
    $phone = clean_text(array_get($data, 'phone', ''), 40);
    $city = clean_text(array_get($data, 'city', ''), 140);
    $address = clean_text(array_get($data, 'address', ''), 255);
    if ($name === '' || $phone === '') {
        return array('ok' => false, 'error' => 'Name and phone are required.');
    }
    try {
        $stmt = $pdo->prepare('UPDATE customers SET full_name = ?, phone = ?, city = ?, address = ?, updated_at = NOW() WHERE id = ?');
        $stmt->execute(array($name, $phone, $city, $address, (int) $customerId));
        return array('ok' => true, 'error' => '');
    } catch (Exception $e) {
        return array('ok' => false, 'error' => 'Profile could not be updated.');
    }
}

function customer_update_password($customerId, $currentPassword, $newPassword)
{
    $pdo = db();
    $customer = customer_find_by_id($customerId);
    if (!$pdo || !$customer) {
        return array('ok' => false, 'error' => 'Account not found.');
    }
    if (!password_verify((string) $currentPassword, $customer['password_hash'])) {
        return array('ok' => false, 'error' => 'Current password is incorrect.');
    }
    if (strlen((string) $newPassword) < 8) {
        return array('ok' => false, 'error' => 'New password must be at least 8 characters.');
    }
    try {
        $stmt = $pdo->prepare('UPDATE customers SET password_hash = ?, updated_at = NOW() WHERE id = ?');
        $stmt->execute(array(password_hash((string) $newPassword, PASSWORD_DEFAULT), (int) $customerId));
        return array('ok' => true, 'error' => '');
    } catch (Exception $e) {
        return array('ok' => false, 'error' => 'Password could not be updated.');
    }
}

function customer_attach_to_request(&$request)
{
    $customer = customer_current();
    if ($customer) {
        $request['customer_id'] = (int) $customer['id'];
    }
}

function customer_form_value($field, $fallback)
{
    if (isset($_POST[$field])) {
        return $_POST[$field];
    }
    $customer = customer_current();
    if (!$customer) {
        return $fallback;
    }
    $map = array(
        'name' => 'full_name',
        'full_name' => 'full_name',
        'phone' => 'phone',
        'email' => 'email',
        'city' => 'city',
        'address' => 'address',
    );
    $key = isset($map[$field]) ? $map[$field] : $field;
    return array_get($customer, $key, $fallback);
}

function customer_dashboard_counts($customer)
{
    $counts = array('orders' => 0, 'reup' => 0, 'custom' => 0, 'unpaid' => 0);
    $pdo = db();
    if (!$pdo || !$customer) {
        return $counts;
    }
    $id = (int) array_get($customer, 'id', 0);
    $email = (string) array_get($customer, 'email', '');
    try {
        $stmt = $pdo->prepare('SELECT COUNT(*) FROM orders WHERE customer_id = ? OR customer_email = ?');
        $stmt->execute(array($id, $email));
        $counts['orders'] = (int) $stmt->fetchColumn();
        $stmt = $pdo->prepare('SELECT COUNT(*) FROM orders WHERE (customer_id = ? OR customer_email = ?) AND payment_status NOT IN (\'Paid\', \'Refunded\')');
        $stmt->execute(array($id, $email));
        $counts['unpaid'] = (int) $stmt->fetchColumn();
        $stmt = $pdo->prepare('SELECT COUNT(*) FROM reup_requests WHERE customer_id = ? OR customer_email = ?');
        $stmt->execute(array($id, $email));
        $counts['reup'] = (int) $stmt->fetchColumn();
        $stmt = $pdo->prepare('SELECT COUNT(*) FROM custom_sofa_quotes WHERE customer_id = ? OR customer_email = ?');
        $stmt->execute(array($id, $email));
        $counts['custom'] = (int) $stmt->fetchColumn();
    } catch (Exception $e) {}
    return $counts;
}

function customer_generate_password_reset($email)
{
    $customer = customer_find_by_email($email);
    if (!$customer) {
        return array('ok' => true, 'customer' => null, 'token' => '', 'mail_sent' => false);
    }
    $pdo = db();
    $token = bin2hex(random_token_bytes(32));
    try {
        $stmt = $pdo->prepare('UPDATE customers SET reset_token_hash = ?, reset_expires_at = DATE_ADD(NOW(), INTERVAL 1 HOUR), updated_at = NOW() WHERE id = ?');
        $stmt->execute(array(password_hash($token, PASSWORD_DEFAULT), (int) $customer['id']));
        $url = site_root_url('reset-password.php?c=' . urlencode((string) $customer['id']) . '&t=' . urlencode($token));
        $body = "Hi " . array_get($customer, 'full_name', 'Furnify customer') . ",\n\nOpen this link to reset your Furnify password:\n" . $url . "\n\nThis link expires in 1 hour. If you did not request this, ignore this email.\n\nFurnify";
        $mailSent = customer_send_email($customer['email'], 'Reset your Furnify password', $body);
        return array('ok' => true, 'customer' => $customer, 'token' => $token, 'mail_sent' => $mailSent);
    } catch (Exception $e) {
        return array('ok' => false, 'customer' => null, 'token' => '', 'mail_sent' => false);
    }
}

function customer_reset_password_with_token($customerId, $token, $newPassword)
{
    $customer = customer_find_by_id((int) $customerId);
    if (!$customer) {
        return array('ok' => false, 'error' => 'Invalid reset link.');
    }
    if (strlen((string) $newPassword) < 8) {
        return array('ok' => false, 'error' => 'New password must be at least 8 characters.');
    }
    $hash = (string) array_get($customer, 'reset_token_hash', '');
    $expires = (string) array_get($customer, 'reset_expires_at', '');
    if ($hash === '' || $token === '' || !password_verify((string) $token, $hash)) {
        return array('ok' => false, 'error' => 'Invalid reset link.');
    }
    if ($expires !== '' && strtotime($expires) < time()) {
        return array('ok' => false, 'error' => 'Reset link expired. Please request a new one.');
    }
    $pdo = db();
    try {
        $stmt = $pdo->prepare('UPDATE customers SET password_hash = ?, reset_token_hash = NULL, reset_expires_at = NULL, updated_at = NOW() WHERE id = ?');
        $stmt->execute(array(password_hash((string) $newPassword, PASSWORD_DEFAULT), (int) $customerId));
        start_secure_session();
        $_SESSION['customer_id'] = (int) $customerId;
        unset($_SESSION['last_reset_link']);
        return array('ok' => true, 'error' => '');
    } catch (Exception $e) {
        return array('ok' => false, 'error' => 'Password could not be reset.');
    }
}
