<?php
// Session configuration
session_start();

// Database configuration
define('DB_PATH', __DIR__ . '/../db/markers.db');

// Create db directory if it doesn't exist
if (!is_dir(__DIR__ . '/../db')) {
    mkdir(__DIR__ . '/../db', 0755, true);
}

// Connect to SQLite database
function getDB() {
    try {
        $db = new PDO('sqlite:' . DB_PATH);
        $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $db->exec('PRAGMA foreign_keys = ON');

        // Initialize database schema
        initializeDB($db);

        return $db;
    } catch (PDOException $e) {
        die(json_encode(['error' => 'Database connection failed: ' . $e->getMessage()]));
    }
}

// Initialize database schema
function initializeDB($db) {
    // Create markers table for points
    $db->exec('
        CREATE TABLE IF NOT EXISTS markers (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name VARCHAR(255) NOT NULL,
            nomor_spbu VARCHAR(100),
            latitude REAL NOT NULL,
            longitude REAL NOT NULL,
            open_24_hours BOOLEAN DEFAULT 0,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP
        )
    ');

    // Add nomor_spbu column to existing databases if it doesn't exist
    try {
        $db->exec('PRAGMA table_info(markers)');
        $columns = $db->query('PRAGMA table_info(markers)')->fetchAll(PDO::FETCH_ASSOC);
        $hasNomorSpbu = false;

        foreach ($columns as $col) {
            if ($col['name'] === 'nomor_spbu') {
                $hasNomorSpbu = true;
                break;
            }
        }

        if (!$hasNomorSpbu) {
            $db->exec('ALTER TABLE markers ADD COLUMN nomor_spbu VARCHAR(100)');
        }
    } catch (PDOException $e) {
        // Table already has the column or other non-critical error
    }

    // Create roads table for polylines
    $db->exec('
        CREATE TABLE IF NOT EXISTS roads (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name VARCHAR(255) NOT NULL,
            road_type VARCHAR(50) NOT NULL CHECK(road_type IN (\'Nasional\', \'Provinsi\', \'Kabupaten\')),
            coordinates TEXT NOT NULL,
            length_meters REAL NOT NULL,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
        )
    ');

    // Create indexes for roads table
    try {
        $db->exec('CREATE INDEX IF NOT EXISTS idx_roads_road_type ON roads(road_type)');
        $db->exec('CREATE INDEX IF NOT EXISTS idx_roads_created_at ON roads(created_at)');
    } catch (PDOException $e) {
        // Indexes may already exist
    }

    // Create land_parcels table for polygons
    $db->exec('
        CREATE TABLE IF NOT EXISTS land_parcels (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name VARCHAR(255) NOT NULL,
            certificate_type VARCHAR(50) NOT NULL CHECK(certificate_type IN (\'SHM\', \'HGB\', \'HGU\', \'HP\')),
            coordinates TEXT NOT NULL,
            area_sqm REAL NOT NULL,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
        )
    ');

    // Create indexes for land_parcels table
    try {
        $db->exec('CREATE INDEX IF NOT EXISTS idx_parcels_cert_type ON land_parcels(certificate_type)');
        $db->exec('CREATE INDEX IF NOT EXISTS idx_parcels_created_at ON land_parcels(created_at)');
    } catch (PDOException $e) {
        // Indexes may already exist
    }

    // Add condition_status column to roads table if it doesn't exist
    try {
        $columns = $db->query('PRAGMA table_info(roads)')->fetchAll(PDO::FETCH_ASSOC);
        $hasConditionStatus = false;

        foreach ($columns as $col) {
            if ($col['name'] === 'condition_status') {
                $hasConditionStatus = true;
                break;
            }
        }

        if (!$hasConditionStatus) {
            $db->exec('ALTER TABLE roads ADD COLUMN condition_status VARCHAR(20) DEFAULT \'normal\' CHECK(condition_status IN (\'normal\', \'minor_damage\', \'major_damage\'))');
        }
    } catch (PDOException $e) {
        // Column may already exist
    }

    // Create pontianak_areas table for choropleth boundaries
    $db->exec('
        CREATE TABLE IF NOT EXISTS pontianak_areas (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name VARCHAR(255) NOT NULL,
            area_type VARCHAR(50) NOT NULL CHECK(area_type IN (\'kelurahan\', \'kecamatan\', \'city\')),
            geometry TEXT NOT NULL,
            population INT DEFAULT 0,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            UNIQUE(name, area_type)
        )
    ');

    // Create indexes for pontianak_areas table
    try {
        $db->exec('CREATE INDEX IF NOT EXISTS idx_pontianak_areas_type ON pontianak_areas(area_type)');
        $db->exec('CREATE INDEX IF NOT EXISTS idx_pontianak_areas_name ON pontianak_areas(name)');
    } catch (PDOException $e) {
        // Indexes may already exist
    }

    // Create area_statistics table for computed densities
    $db->exec('
        CREATE TABLE IF NOT EXISTS area_statistics (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            pontianak_area_id INTEGER NOT NULL,
            statistic_type VARCHAR(50) NOT NULL CHECK(statistic_type IN (\'population_density\', \'road_density\', \'damaged_roads_density\')),
            value REAL NOT NULL,
            data_date DATE NOT NULL,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY(pontianak_area_id) REFERENCES pontianak_areas(id) ON DELETE CASCADE,
            UNIQUE(pontianak_area_id, statistic_type, data_date)
        )
    ');

    // Create indexes for area_statistics table
    try {
        $db->exec('CREATE INDEX IF NOT EXISTS idx_stats_area_type ON area_statistics(pontianak_area_id, statistic_type)');
    } catch (PDOException $e) {
        // Indexes may already exist
    }

    // Create masjids table
    $db->exec('
        CREATE TABLE IF NOT EXISTS masjids (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name VARCHAR(255) NOT NULL,
            latitude REAL NOT NULL,
            longitude REAL NOT NULL,
            radius_meters REAL NOT NULL DEFAULT 500,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
        )
    ');

    // Create indexes for masjids table
    try {
        $db->exec('CREATE INDEX IF NOT EXISTS idx_masjids_created_at ON masjids(created_at)');
        $db->exec('CREATE INDEX IF NOT EXISTS idx_masjids_name ON masjids(name)');
    } catch (PDOException $e) {
        // Indexes may already exist
    }

    // Create special needs points table
    $db->exec('
        CREATE TABLE IF NOT EXISTS need_points (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name VARCHAR(255) NOT NULL,
            latitude REAL NOT NULL,
            longitude REAL NOT NULL,
            masjid_id INTEGER NOT NULL,
            household_name VARCHAR(255),
            head_name VARCHAR(255),
            economic_status VARCHAR(50),
            is_verified INTEGER NOT NULL DEFAULT 0,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY(masjid_id) REFERENCES masjids(id) ON DELETE CASCADE
        )
    ');

    // Create indexes for need_points table
    try {
        $db->exec('CREATE INDEX IF NOT EXISTS idx_need_points_masjid_id ON need_points(masjid_id)');
        $db->exec('CREATE INDEX IF NOT EXISTS idx_need_points_created_at ON need_points(created_at)');
    } catch (PDOException $e) {
        // Indexes may already exist
    }

    // Add new columns to existing need_points table if they don't exist
    try {
        $columns = $db->query('PRAGMA table_info(need_points)')->fetchAll(PDO::FETCH_ASSOC);
        $hasHouseholdName = false;
        $hasHeadName = false;
        $hasEconomic = false;
        $hasVerified = false;
        foreach ($columns as $col) {
            if ($col['name'] === 'household_name') $hasHouseholdName = true;
            if ($col['name'] === 'head_name') $hasHeadName = true;
            if ($col['name'] === 'economic_status') $hasEconomic = true;
            if ($col['name'] === 'is_verified') $hasVerified = true;
        }
        if (!$hasHouseholdName) {
            $db->exec('ALTER TABLE need_points ADD COLUMN household_name VARCHAR(255)');
        }
        if (!$hasHeadName) {
            $db->exec('ALTER TABLE need_points ADD COLUMN head_name VARCHAR(255)');
        }
        if (!$hasEconomic) {
            $db->exec('ALTER TABLE need_points ADD COLUMN economic_status VARCHAR(50)');
        }
        if (!$hasVerified) {
            $db->exec('ALTER TABLE need_points ADD COLUMN is_verified INTEGER NOT NULL DEFAULT 0');
        }
    } catch (PDOException $e) {
        // non-critical
    }

    // Create households table for poverty alleviation base data
    $db->exec('
        CREATE TABLE IF NOT EXISTS households (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            household_code VARCHAR(100) NOT NULL UNIQUE,
            head_name VARCHAR(255) NOT NULL,
            nik_hash VARCHAR(255),
            latitude REAL NOT NULL,
            longitude REAL NOT NULL,
            need_point_id INTEGER UNIQUE,
            masjid_id INTEGER,
            pontianak_area_id INTEGER,
            monthly_income REAL NOT NULL DEFAULT 0,
            dependents_count INTEGER NOT NULL DEFAULT 0,
            housing_condition_score INTEGER NOT NULL DEFAULT 3 CHECK(housing_condition_score BETWEEN 1 AND 5),
            employment_status VARCHAR(50) NOT NULL DEFAULT \'unknown\' CHECK(employment_status IN (\'formal\', \'informal\', \'unemployed\', \'daily_worker\', \'micro_business\', \'retired\', \'unknown\')),
            economic_status VARCHAR(50) NOT NULL DEFAULT \'unknown\' CHECK(economic_status IN (\'sangat_miskin\', \'miskin\', \'rentan\', \'cukup\', \'baik\', \'unknown\')),
            verification_status VARCHAR(30) NOT NULL DEFAULT \'unverified\' CHECK(verification_status IN (\'unverified\', \'field_verified\', \'admin_approved\', \'rejected\', \'needs_review\')),
            vulnerability_notes TEXT,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY(need_point_id) REFERENCES need_points(id) ON DELETE SET NULL,
            FOREIGN KEY(masjid_id) REFERENCES masjids(id) ON DELETE SET NULL,
            FOREIGN KEY(pontianak_area_id) REFERENCES pontianak_areas(id) ON DELETE SET NULL
        )
    ');

    // Create indexes for households table
    try {
        $db->exec('CREATE INDEX IF NOT EXISTS idx_households_code ON households(household_code)');
        $db->exec('CREATE INDEX IF NOT EXISTS idx_households_verification_status ON households(verification_status)');
        $db->exec('CREATE INDEX IF NOT EXISTS idx_households_masjid_id ON households(masjid_id)');
        $db->exec('CREATE INDEX IF NOT EXISTS idx_households_area_id ON households(pontianak_area_id)');
        $db->exec('CREATE INDEX IF NOT EXISTS idx_households_created_at ON households(created_at)');
    } catch (PDOException $e) {
        // Indexes may already exist
    }

    // Add economic_status column to existing households table if it doesn't exist
    try {
        $columns = $db->query('PRAGMA table_info(households)')->fetchAll(PDO::FETCH_ASSOC);
        $hasEconomicStatus = false;
        foreach ($columns as $col) {
            if ($col['name'] === 'economic_status') {
                $hasEconomicStatus = true;
                break;
            }
        }
        if (!$hasEconomicStatus) {
            $db->exec("ALTER TABLE households ADD COLUMN economic_status VARCHAR(50) NOT NULL DEFAULT 'unknown'");
        }
    } catch (PDOException $e) {
        // non-critical
    }

    // Create assistance distributions table
    $db->exec('
        CREATE TABLE IF NOT EXISTS assistance_distributions (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            household_id INTEGER NOT NULL,
            assistance_type VARCHAR(50) NOT NULL CHECK(assistance_type IN (\'pangan\', \'tunai\', \'kesehatan\', \'pendidikan\', \'usaha_mikro\', \'lainnya\')),
            assistance_value REAL NOT NULL DEFAULT 0,
            assistance_frequency VARCHAR(30) NOT NULL DEFAULT \'sekali\' CHECK(assistance_frequency IN (\'sekali\', \'mingguan\', \'bulanan\', \'triwulanan\', \'tahunan\', \'insidental\')),
            distribution_date DATE NOT NULL,
            delivery_status VARCHAR(30) NOT NULL DEFAULT \'pending\' CHECK(delivery_status IN (\'pending\', \'delivered\', \'failed\')),
            delivered_by VARCHAR(255),
            notes TEXT,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY(household_id) REFERENCES households(id) ON DELETE CASCADE
        )
    ');

    // Create indexes for assistance distributions table
    try {
        $db->exec('CREATE INDEX IF NOT EXISTS idx_assistance_household_id ON assistance_distributions(household_id)');
        $db->exec('CREATE INDEX IF NOT EXISTS idx_assistance_date ON assistance_distributions(distribution_date)');
        $db->exec('CREATE INDEX IF NOT EXISTS idx_assistance_status ON assistance_distributions(delivery_status)');
    } catch (PDOException $e) {
        // Indexes may already exist
    }

    // Create verification logs table
    $db->exec('
        CREATE TABLE IF NOT EXISTS verification_logs (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            household_id INTEGER NOT NULL,
            verification_status VARCHAR(30) NOT NULL CHECK(verification_status IN (\'field_verified\', \'admin_approved\', \'rejected\', \'needs_review\')),
            verifier_name VARCHAR(255) NOT NULL,
            verifier_role VARCHAR(100),
            field_note TEXT,
            evidence_photo_count INTEGER NOT NULL DEFAULT 0,
            data_confidence_score REAL,
            verified_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY(household_id) REFERENCES households(id) ON DELETE CASCADE
        )
    ');

    // Create indexes for verification logs table
    try {
        $db->exec('CREATE INDEX IF NOT EXISTS idx_verification_household_id ON verification_logs(household_id)');
        $db->exec('CREATE INDEX IF NOT EXISTS idx_verification_status ON verification_logs(verification_status)');
        $db->exec('CREATE INDEX IF NOT EXISTS idx_verification_verified_at ON verification_logs(verified_at)');
    } catch (PDOException $e) {
        // Indexes may already exist
    }

    // Create household scores table (Option B)
    $db->exec('
        CREATE TABLE IF NOT EXISTS household_scores (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            household_id INTEGER NOT NULL UNIQUE,
            skr REAL NOT NULL DEFAULT 0,
            sag REAL NOT NULL DEFAULT 0,
            spi REAL NOT NULL DEFAULT 0,
            priority_level VARCHAR(30) NOT NULL DEFAULT \'low\' CHECK(priority_level IN (\'very_high\', \'high\', \'medium\', \'low\')),
            score_version VARCHAR(30) NOT NULL DEFAULT \'v1\',
            components_json TEXT,
            computed_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY(household_id) REFERENCES households(id) ON DELETE CASCADE
        )
    ');

    // Create indexes for household_scores table
    try {
        $db->exec('CREATE INDEX IF NOT EXISTS idx_household_scores_spi ON household_scores(spi DESC)');
        $db->exec('CREATE INDEX IF NOT EXISTS idx_household_scores_priority ON household_scores(priority_level)');
        $db->exec('CREATE INDEX IF NOT EXISTS idx_household_scores_computed_at ON household_scores(computed_at)');
    } catch (PDOException $e) {
        // Indexes may already exist
    }

    // Create score metadata table for versioning/tuning
    $db->exec('
        CREATE TABLE IF NOT EXISTS score_metadata (
            key_name VARCHAR(100) PRIMARY KEY,
            key_value TEXT NOT NULL,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
        )
    ');

    // Create users table for authentication
    $db->exec('
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username VARCHAR(100) NOT NULL UNIQUE,
            email VARCHAR(255) NOT NULL UNIQUE,
            password_hash VARCHAR(255) NOT NULL,
            role VARCHAR(50) NOT NULL DEFAULT \'Petugas\' CHECK(role IN (\'Admin\', \'Petugas\', \'Pimpinan\')),
            is_active BOOLEAN NOT NULL DEFAULT 1,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
        )
    ');

    // Create indexes for users table
    try {
        $db->exec('CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)');
        $db->exec('CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)');
        $db->exec('CREATE INDEX IF NOT EXISTS idx_users_role ON users(role)');
    } catch (PDOException $e) {
        // Indexes may already exist
    }

    // Initialize default admin user
    try {
        $adminCount = $db->query('SELECT COUNT(*) as cnt FROM users WHERE role = \'Admin\'')->fetch(PDO::FETCH_ASSOC);
        if ($adminCount['cnt'] == 0) {
            $adminPassword = password_hash('admin123', PASSWORD_BCRYPT, ['cost' => 12]);
            $stmt = $db->prepare('INSERT INTO users (username, email, password_hash, role, is_active) VALUES (?, ?, ?, ?, 1)');
            $stmt->execute(['admin', 'admin@system.local', $adminPassword, 'Admin']);
        }
    } catch (PDOException $e) {
        // User may already exist
    }

    // Initialize default score metadata values
    try {
        $defaultMeta = [
            'score_version' => 'v1',
            'skr_weights' => '{"I":0.25,"E":0.20,"D":0.15,"H":0.15,"A":0.15,"S":0.10}',
            'sag_weights' => '{"J":0.40,"R":0.35,"T":0.25}',
            'spi_weights' => '{"SKR":0.60,"SAG_INV":0.30,"K":0.10}',
            'income_max_reference' => '5000000',
            'distance_max_reference' => '2000',
            'target_nasional' => '7.50',
            'population_reference' => '288315899'
        ];

        $metaStmt = $db->prepare('INSERT OR IGNORE INTO score_metadata (key_name, key_value) VALUES (?, ?)');
        foreach ($defaultMeta as $key => $value) {
            $metaStmt->execute([$key, $value]);
        }
    } catch (PDOException $e) {
        // Metadata init may already exist
    }
}

function haversineDistanceMeters($lat1, $lng1, $lat2, $lng2) {
    $earthRadius = 6371000; // meters

    $lat1Rad = deg2rad((float)$lat1);
    $lat2Rad = deg2rad((float)$lat2);
    $dLat = deg2rad((float)$lat2 - (float)$lat1);
    $dLng = deg2rad((float)$lng2 - (float)$lng1);

    $a = sin($dLat / 2) * sin($dLat / 2) +
        cos($lat1Rad) * cos($lat2Rad) * sin($dLng / 2) * sin($dLng / 2);
    $c = 2 * atan2(sqrt($a), sqrt(1 - $a));

    return $earthRadius * $c;
}

function normalizeMasjidRadius($radiusValue, &$error = null) {
    $error = null;

    if ($radiusValue === null || $radiusValue === '') {
        return 500.0;
    }

    if (!is_numeric($radiusValue)) {
        $error = 'Radius must be a numeric value';
        return null;
    }

    $radius = (float)$radiusValue;
    if ($radius < 50 || $radius > 5000) {
        $error = 'Radius must be between 50 and 5000 meters';
        return null;
    }

    return $radius;
}

function findNearestMasjidCoveringPoint($db, $latitude, $longitude) {
    $stmt = $db->query('SELECT id, name, latitude, longitude, radius_meters FROM masjids');
    $masjids = $stmt->fetchAll(PDO::FETCH_ASSOC);

    $nearest = null;
    $nearestDistance = null;

    foreach ($masjids as $masjid) {
        $distance = haversineDistanceMeters(
            (float)$latitude,
            (float)$longitude,
            (float)$masjid['latitude'],
            (float)$masjid['longitude']
        );

        if ($distance <= (float)$masjid['radius_meters']) {
            if ($nearestDistance === null || $distance < $nearestDistance) {
                $nearest = $masjid;
                $nearestDistance = $distance;
            }
        }
    }

    if ($nearest === null) {
        return null;
    }

    $nearest['distance_meters'] = $nearestDistance;
    return $nearest;
}

function getNeedPointWithMasjid($db, $id) {
    $stmt = $db->prepare('
        SELECT
            np.id,
            np.name,
            np.latitude,
            np.longitude,
            np.masjid_id,
            np.household_name,
            np.head_name,
            np.economic_status,
            np.is_verified,
            np.created_at,
            m.name AS masjid_name,
            m.latitude AS masjid_latitude,
            m.longitude AS masjid_longitude,
            m.radius_meters
        FROM need_points np
        JOIN masjids m ON m.id = np.masjid_id
        WHERE np.id = ?
    ');
    $stmt->execute([$id]);
    return $stmt->fetch(PDO::FETCH_ASSOC);
}

function cleanupNeedPointsOutsideMasjidCoverage($db, $masjidId) {
    $masjidStmt = $db->prepare('SELECT id, latitude, longitude, radius_meters FROM masjids WHERE id = ?');
    $masjidStmt->execute([$masjidId]);
    $masjid = $masjidStmt->fetch(PDO::FETCH_ASSOC);

    if (!$masjid) {
        return [
            'deleted_count' => 0,
            'deleted_ids' => []
        ];
    }

    $pointsStmt = $db->prepare('SELECT id, latitude, longitude FROM need_points WHERE masjid_id = ?');
    $pointsStmt->execute([$masjidId]);
    $points = $pointsStmt->fetchAll(PDO::FETCH_ASSOC);

    $idsToDelete = [];
    foreach ($points as $point) {
        $distance = haversineDistanceMeters(
            (float)$point['latitude'],
            (float)$point['longitude'],
            (float)$masjid['latitude'],
            (float)$masjid['longitude']
        );

        if ($distance > (float)$masjid['radius_meters']) {
            $idsToDelete[] = (int)$point['id'];
        }
    }

    if (!empty($idsToDelete)) {
        $placeholders = implode(',', array_fill(0, count($idsToDelete), '?'));
        $deleteStmt = $db->prepare("DELETE FROM need_points WHERE id IN ($placeholders)");
        $deleteStmt->execute($idsToDelete);
    }

    return [
        'deleted_count' => count($idsToDelete),
        'deleted_ids' => $idsToDelete
    ];
}

function validateCoordinates($latitude, $longitude, &$error = null) {
    $error = null;

    if (!is_numeric($latitude) || !is_numeric($longitude)) {
        $error = 'Latitude and longitude must be numeric values';
        return false;
    }

    $lat = (float)$latitude;
    $lng = (float)$longitude;

    if ($lat < -90 || $lat > 90) {
        $error = 'Latitude must be between -90 and 90';
        return false;
    }

    if ($lng < -180 || $lng > 180) {
        $error = 'Longitude must be between -180 and 180';
        return false;
    }

    return true;
}

function normalizeHouseholdCode($db, $inputCode = null) {
    $code = trim((string)($inputCode ?? ''));
    if ($code !== '') {
        return $code;
    }

    do {
        try {
            $random = strtoupper(substr(bin2hex(random_bytes(3)), 0, 6));
        } catch (Exception $e) {
            $random = strtoupper(substr(md5((string)mt_rand()), 0, 6));
        }

        $code = 'HH-' . date('Ymd') . '-' . $random;
        $checkStmt = $db->prepare('SELECT id FROM households WHERE household_code = ? LIMIT 1');
        $checkStmt->execute([$code]);
        $exists = $checkStmt->fetch(PDO::FETCH_ASSOC);
    } while ($exists);

    return $code;
}

function normalizeMonthlyIncome($value, &$error = null) {
    $error = null;
    if ($value === null || $value === '') {
        return 0.0;
    }

    if (!is_numeric($value)) {
        $error = 'Monthly income must be numeric';
        return null;
    }

    $income = (float)$value;
    if ($income < 0 || $income > 1000000000) {
        $error = 'Monthly income must be between 0 and 1000000000';
        return null;
    }

    return $income;
}

function normalizeDependentsCount($value, &$error = null) {
    $error = null;
    if ($value === null || $value === '') {
        return 0;
    }

    if (!is_numeric($value)) {
        $error = 'Dependents count must be numeric';
        return null;
    }

    $count = (int)$value;
    if ($count < 0 || $count > 50) {
        $error = 'Dependents count must be between 0 and 50';
        return null;
    }

    return $count;
}

function normalizeHousingConditionScore($value, &$error = null) {
    $error = null;
    if ($value === null || $value === '') {
        return 3;
    }

    if (!is_numeric($value)) {
        $error = 'Housing condition score must be numeric';
        return null;
    }

    $score = (int)$value;
    if ($score < 1 || $score > 5) {
        $error = 'Housing condition score must be between 1 and 5';
        return null;
    }

    return $score;
}

function normalizeEmploymentStatus($value, &$error = null) {
    $error = null;
    $allowed = ['formal', 'informal', 'unemployed', 'daily_worker', 'micro_business', 'retired', 'unknown'];

    if ($value === null || $value === '') {
        return 'unknown';
    }

    $status = strtolower(trim((string)$value));
    if (!in_array($status, $allowed, true)) {
        $error = 'Employment status is invalid';
        return null;
    }

    return $status;
}

function normalizeVerificationStatus($value, &$error = null, $allowUnverified = true) {
    $error = null;
    $allowed = ['field_verified', 'admin_approved', 'rejected', 'needs_review'];
    if ($allowUnverified) {
        $allowed[] = 'unverified';
    }

    if ($value === null || $value === '') {
        return $allowUnverified ? 'unverified' : 'needs_review';
    }

    $status = strtolower(trim((string)$value));
    if (!in_array($status, $allowed, true)) {
        $error = 'Verification status is invalid';
        return null;
    }

    return $status;
}

function normalizeAssistanceType($value, &$error = null) {
    $error = null;
    $allowed = ['pangan', 'tunai', 'kesehatan', 'pendidikan', 'usaha_mikro', 'lainnya'];

    if ($value === null || $value === '') {
        $error = 'Assistance type is required';
        return null;
    }

    $type = strtolower(trim((string)$value));
    if (!in_array($type, $allowed, true)) {
        $error = 'Assistance type is invalid';
        return null;
    }

    return $type;
}

function normalizeAssistanceFrequency($value, &$error = null) {
    $error = null;
    $allowed = ['sekali', 'mingguan', 'bulanan', 'triwulanan', 'tahunan', 'insidental'];

    if ($value === null || $value === '') {
        return 'sekali';
    }

    $frequency = strtolower(trim((string)$value));
    if (!in_array($frequency, $allowed, true)) {
        $error = 'Assistance frequency is invalid';
        return null;
    }

    return $frequency;
}

function normalizeDeliveryStatus($value, &$error = null) {
    $error = null;
    $allowed = ['pending', 'delivered', 'failed'];

    if ($value === null || $value === '') {
        return 'pending';
    }

    $status = strtolower(trim((string)$value));
    if (!in_array($status, $allowed, true)) {
        $error = 'Delivery status is invalid';
        return null;
    }

    return $status;
}

function normalizeAssistanceValue($value, &$error = null) {
    $error = null;
    if ($value === null || $value === '') {
        return 0.0;
    }

    if (!is_numeric($value)) {
        $error = 'Assistance value must be numeric';
        return null;
    }

    $assistanceValue = (float)$value;
    if ($assistanceValue < 0 || $assistanceValue > 1000000000) {
        $error = 'Assistance value must be between 0 and 1000000000';
        return null;
    }

    return $assistanceValue;
}

function normalizeEvidencePhotoCount($value, &$error = null) {
    $error = null;
    if ($value === null || $value === '') {
        return 0;
    }

    if (!is_numeric($value)) {
        $error = 'Evidence photo count must be numeric';
        return null;
    }

    $count = (int)$value;
    if ($count < 0 || $count > 100) {
        $error = 'Evidence photo count must be between 0 and 100';
        return null;
    }

    return $count;
}

function normalizeDataConfidenceScore($value, &$error = null) {
    $error = null;
    if ($value === null || $value === '') {
        return null;
    }

    if (!is_numeric($value)) {
        $error = 'Data confidence score must be numeric';
        return null;
    }

    $score = (float)$value;
    if ($score < 0 || $score > 1) {
        $error = 'Data confidence score must be between 0 and 1';
        return null;
    }

    return $score;
}

function normalizeNikHashFromInput($nikInput = null) {
    if ($nikInput === null) {
        return null;
    }

    $nik = trim((string)$nikInput);
    if ($nik === '') {
        return null;
    }

    return hash('sha256', $nik);
}

function householdExists($db, $householdId) {
    $stmt = $db->prepare('SELECT id FROM households WHERE id = ? LIMIT 1');
    $stmt->execute([(int)$householdId]);
    return (bool)$stmt->fetch(PDO::FETCH_ASSOC);
}

function getHouseholdById($db, $householdId) {
    $stmt = $db->prepare('
        SELECT
            h.*,
            np.name AS need_point_name,
            m.name AS masjid_name,
            pa.name AS area_name,
            (
                SELECT COUNT(*)
                FROM assistance_distributions ad
                WHERE ad.household_id = h.id
            ) AS assistance_count,
            (
                SELECT MAX(vl.verified_at)
                FROM verification_logs vl
                WHERE vl.household_id = h.id
            ) AS last_verified_at
        FROM households h
        LEFT JOIN need_points np ON np.id = h.need_point_id
        LEFT JOIN masjids m ON m.id = h.masjid_id
        LEFT JOIN pontianak_areas pa ON pa.id = h.pontianak_area_id
        WHERE h.id = ?
    ');
    $stmt->execute([(int)$householdId]);
    return $stmt->fetch(PDO::FETCH_ASSOC);
}

function mapEconomicVulnerability($economicStatus) {
    $status = strtolower(trim((string)$economicStatus));
    $scores = [
        'sangat_miskin' => 1.0,
        'miskin' => 0.85,
        'rentan' => 0.65,
        'cukup' => 0.35,
        'baik' => 0.15,
        'unknown' => 0.5
    ];

    return isset($scores[$status]) ? $scores[$status] : 0.5;
}

function syncNeedPointsToHouseholds($db) {
    $updatedCount = 0;
    $insertedCount = 0;

    $updateStmt = $db->prepare("\n        UPDATE households\n        SET economic_status = (\n            SELECT COALESCE(NULLIF(TRIM(np.economic_status), ''), 'unknown')\n            FROM need_points np\n            WHERE np.id = households.need_point_id\n        ),\n        updated_at = CURRENT_TIMESTAMP\n        WHERE need_point_id IS NOT NULL\n          AND (economic_status IS NULL OR TRIM(economic_status) = '')\n    ");
    $updateStmt->execute();
    $updatedCount += $updateStmt->rowCount();

    $missingStmt = $db->query("\n        SELECT\n            np.id,\n            np.name,\n            np.latitude,\n            np.longitude,\n            np.masjid_id,\n            np.head_name,\n            np.economic_status,\n            np.is_verified\n        FROM need_points np\n        LEFT JOIN households h ON h.need_point_id = np.id\n        WHERE h.id IS NULL\n        ORDER BY np.id ASC\n    ");
    $missingRows = $missingStmt->fetchAll(PDO::FETCH_ASSOC);

    if (!empty($missingRows)) {
        $insertStmt = $db->prepare("\n            INSERT INTO households (\n                household_code,\n                head_name,\n                latitude,\n                longitude,\n                need_point_id,\n                masjid_id,\n                monthly_income,\n                dependents_count,\n                housing_condition_score,\n                employment_status,\n                economic_status,\n                verification_status,\n                vulnerability_notes\n            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n        ");

        foreach ($missingRows as $row) {
            $pointId = (int)$row['id'];
            $householdCode = 'NP-' . str_pad((string)$pointId, 6, '0', STR_PAD_LEFT);
            $economicStatus = strtolower(trim((string)($row['economic_status'] ?? 'unknown')));
            if ($economicStatus === '') {
                $economicStatus = 'unknown';
            }
            $verificationStatus = ((int)($row['is_verified'] ?? 0) === 1) ? 'field_verified' : 'unverified';
            $headName = trim((string)($row['head_name'] ?? ''));
            if ($headName === '') {
                $headName = 'Kepala ' . $householdCode;
            }

            $insertStmt->execute([
                $householdCode,
                $headName,
                (float)$row['latitude'],
                (float)$row['longitude'],
                $pointId,
                !empty($row['masjid_id']) ? (int)$row['masjid_id'] : null,
                0,
                0,
                3,
                'unknown',
                $economicStatus,
                $verificationStatus,
                'Auto-synced from need_point #' . $pointId
            ]);
            $insertedCount++;
        }
    }

    return [
        'updated_count' => $updatedCount,
        'inserted_count' => $insertedCount,
        'total_changed' => $updatedCount + $insertedCount
    ];
}

function ensureHouseholdScoresForAllHouseholds($db) {
    $missingStmt = $db->query("\n        SELECT h.*\n        FROM households h\n        LEFT JOIN household_scores hs ON hs.household_id = h.id\n        WHERE hs.household_id IS NULL\n        ORDER BY h.id ASC\n    ");
    $missingHouseholds = $missingStmt->fetchAll(PDO::FETCH_ASSOC);

    $computed = 0;
    foreach ($missingHouseholds as $household) {
        $scoreData = computeHouseholdScores($db, $household);
        upsertHouseholdScore($db, $household['id'], $scoreData);
        $computed++;
    }

    return $computed;
}

function clamp01($value) {
    $v = (float)$value;
    if ($v < 0) return 0.0;
    if ($v > 1) return 1.0;
    return $v;
}

function normalizeToUnit($value, $minValue, $maxValue, $invert = false) {
    $minV = (float)$minValue;
    $maxV = (float)$maxValue;

    if ($maxV <= $minV) {
        return 0.0;
    }

    $normalized = ((float)$value - $minV) / ($maxV - $minV);
    $normalized = clamp01($normalized);

    if ($invert) {
        return 1.0 - $normalized;
    }

    return $normalized;
}

function getLatestAreaStatisticValue($db, $areaId, $statType) {
    if (!$areaId || !$statType) {
        return null;
    }

    $stmt = $db->prepare('
        SELECT value
        FROM area_statistics
        WHERE pontianak_area_id = ? AND statistic_type = ?
        ORDER BY data_date DESC, created_at DESC
        LIMIT 1
    ');
    $stmt->execute([(int)$areaId, (string)$statType]);
    $row = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$row) {
        return null;
    }

    return (float)$row['value'];
}

function getHouseholdDistanceToMasjidMeters($householdRow, $masjidRow) {
    if (!$householdRow || !$masjidRow) {
        return null;
    }

    return haversineDistanceMeters(
        (float)$householdRow['latitude'],
        (float)$householdRow['longitude'],
        (float)$masjidRow['latitude'],
        (float)$masjidRow['longitude']
    );
}

function mapEmploymentVulnerability($employmentStatus) {
    $status = strtolower(trim((string)$employmentStatus));
    $scores = [
        'unemployed' => 1.0,
        'daily_worker' => 0.85,
        'informal' => 0.7,
        'micro_business' => 0.55,
        'retired' => 0.4,
        'formal' => 0.25,
        'unknown' => 0.5
    ];

    return isset($scores[$status]) ? $scores[$status] : 0.5;
}

function mapShockFactor($verificationStatus) {
    $status = strtolower(trim((string)$verificationStatus));
    $map = [
        'needs_review' => 1.0,
        'unverified' => 0.7,
        'rejected' => 0.6,
        'field_verified' => 0.4,
        'admin_approved' => 0.2
    ];

    return isset($map[$status]) ? $map[$status] : 0.5;
}

function classifyPriorityLevel($spi) {
    $value = (float)$spi;
    if ($value >= 0.75) {
        return 'very_high';
    }
    if ($value >= 0.60) {
        return 'high';
    }
    if ($value >= 0.45) {
        return 'medium';
    }

    return 'low';
}

function computeHouseholdScores($db, $householdRow) {
    $income = (float)($householdRow['monthly_income'] ?? 0);
    $dependents = (int)($householdRow['dependents_count'] ?? 0);
    $housingScore = (int)($householdRow['housing_condition_score'] ?? 3);
    $employmentStatus = $householdRow['employment_status'] ?? 'unknown';
    $economicStatus = $householdRow['economic_status'] ?? 'unknown';
    $verificationStatus = $householdRow['verification_status'] ?? 'unverified';

    // SKR components (higher = more vulnerable)
    $economicVulnerability = mapEconomicVulnerability($economicStatus);        // primary poverty condition
    $incomeDeprivation = normalizeToUnit($income, 0, 5000000, true);          // supporting signal
    $employmentVulnerability = mapEmploymentVulnerability($employmentStatus);  // E
    $dependencyBurden = normalizeToUnit($dependents, 0, 6, false);            // D
    $housingVulnerability = normalizeToUnit($housingScore, 1, 5, true);       // H

    // A: accessibility deprivation (inverse from distance to masjid)
    $masjidDistanceMeters = null;
    $accessibilityService = 0.5;
    if (!empty($householdRow['masjid_id'])) {
        $masjidStmt = $db->prepare('SELECT latitude, longitude FROM masjids WHERE id = ?');
        $masjidStmt->execute([(int)$householdRow['masjid_id']]);
        $masjid = $masjidStmt->fetch(PDO::FETCH_ASSOC);

        if ($masjid) {
            $masjidDistanceMeters = getHouseholdDistanceToMasjidMeters($householdRow, $masjid);
            $distanceAccessScore = normalizeToUnit($masjidDistanceMeters, 0, 2000, true); // J as accessibility
            $accessibilityService = 1.0 - $distanceAccessScore; // convert to deprivation
        }
    }

    // S: shock factor from current verification confidence
    $shockFactor = mapShockFactor($verificationStatus);

    $skr =
        (0.40 * $economicVulnerability) +
        (0.18 * $incomeDeprivation) +
        (0.12 * $employmentVulnerability) +
        (0.10 * $dependencyBurden) +
        (0.10 * $housingVulnerability) +
        (0.06 * $accessibilityService) +
        (0.04 * $shockFactor);
    $skr = round(clamp01($skr), 6);

    // SAG components (higher = better accessibility)
    $J = 0.5;
    if ($masjidDistanceMeters !== null) {
        $J = normalizeToUnit($masjidDistanceMeters, 0, 2000, true);
    }

    $roadDensity = getLatestAreaStatisticValue($db, $householdRow['pontianak_area_id'] ?? null, 'road_density');
    $R = $roadDensity !== null ? normalizeToUnit($roadDensity, 0, 5, false) : 0.5;

    // T fallback because travel-time data is not available yet.
    $T = ($J + $R) / 2;

    $sag = (0.40 * $J) + (0.35 * $R) + (0.25 * $T);
    $sag = round(clamp01($sag), 6);

    $K = $shockFactor;
    $spi = (0.60 * $skr) + (0.30 * (1 - $sag)) + (0.10 * $K);
    $spi = round(clamp01($spi), 6);

    $priorityLevel = classifyPriorityLevel($spi);

    return [
        'skr' => $skr,
        'sag' => $sag,
        'spi' => $spi,
        'priority_level' => $priorityLevel,
        'score_version' => 'v1',
        'components' => [
            'ECO_economic_vulnerability' => round($economicVulnerability, 6),
            'I_income_deprivation' => round($incomeDeprivation, 6),
            'E_employment_vulnerability' => round($employmentVulnerability, 6),
            'D_dependency_burden' => round($dependencyBurden, 6),
            'H_housing_vulnerability' => round($housingVulnerability, 6),
            'A_accessibility_deprivation' => round($accessibilityService, 6),
            'S_shock_factor' => round($shockFactor, 6),
            'J_distance_access' => round($J, 6),
            'R_road_access' => round($R, 6),
            'T_time_access_proxy' => round($T, 6)
        ]
    ];
}

function upsertHouseholdScore($db, $householdId, $scoreData) {
    $stmt = $db->prepare('
        INSERT INTO household_scores (
            household_id,
            skr,
            sag,
            spi,
            priority_level,
            score_version,
            components_json,
            computed_at,
            updated_at
        )
        VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
        ON CONFLICT(household_id)
        DO UPDATE SET
            skr = excluded.skr,
            sag = excluded.sag,
            spi = excluded.spi,
            priority_level = excluded.priority_level,
            score_version = excluded.score_version,
            components_json = excluded.components_json,
            computed_at = CURRENT_TIMESTAMP,
            updated_at = CURRENT_TIMESTAMP
    ');

    $stmt->execute([
        (int)$householdId,
        (float)$scoreData['skr'],
        (float)$scoreData['sag'],
        (float)$scoreData['spi'],
        (string)$scoreData['priority_level'],
        (string)$scoreData['score_version'],
        json_encode($scoreData['components'])
    ]);
}

// Authentication helper functions
function loginUser($email, $password) {
    try {
        $db = getDB();
        $stmt = $db->prepare('SELECT id, username, email, password_hash, role, is_active FROM users WHERE email = ? AND is_active = 1');
        $stmt->execute([$email]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$user) {
            return ['success' => false, 'message' => 'User not found or inactive'];
        }

        if (!password_verify($password, $user['password_hash'])) {
            return ['success' => false, 'message' => 'Invalid password'];
        }

        // Set session
        $_SESSION['user_id'] = $user['id'];
        $_SESSION['username'] = $user['username'];
        $_SESSION['email'] = $user['email'];
        $_SESSION['role'] = $user['role'];
        $_SESSION['login_time'] = time();

        return [
            'success' => true,
            'message' => 'Login successful',
            'user' => [
                'id' => $user['id'],
                'username' => $user['username'],
                'email' => $user['email'],
                'role' => $user['role']
            ]
        ];
    } catch (PDOException $e) {
        return ['success' => false, 'message' => 'Database error: ' . $e->getMessage()];
    }
}

function logoutUser() {
    session_destroy();
    return ['success' => true, 'message' => 'Logout successful'];
}

function getCurrentUser() {
    if (!isset($_SESSION['user_id'])) {
        return null;
    }

    return [
        'id' => $_SESSION['user_id'],
        'username' => $_SESSION['username'],
        'email' => $_SESSION['email'],
        'role' => $_SESSION['role']
    ];
}

function isUserLoggedIn() {
    return isset($_SESSION['user_id']) && !empty($_SESSION['user_id']);
}

function requireLogin() {
    if (!isUserLoggedIn()) {
        http_response_code(401);
        die(json_encode(['error' => 'Unauthorized - Please login']));
    }
}

function requireRole($allowedRoles) {
    requireLogin();
    
    if (!is_array($allowedRoles)) {
        $allowedRoles = [$allowedRoles];
    }

    if (!in_array($_SESSION['role'], $allowedRoles)) {
        http_response_code(403);
        die(json_encode(['error' => 'Forbidden - Insufficient permissions']));
    }
}

function createUser($username, $email, $password, $role = 'Petugas') {
    try {
        $db = getDB();
        
        // Check if user already exists
        $stmt = $db->prepare('SELECT id FROM users WHERE email = ? OR username = ?');
        $stmt->execute([$email, $username]);
        if ($stmt->fetch()) {
            return ['success' => false, 'message' => 'User already exists'];
        }

        $passwordHash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
        
        $stmt = $db->prepare('
            INSERT INTO users (username, email, password_hash, role, is_active)
            VALUES (?, ?, ?, ?, 1)
        ');
        $stmt->execute([$username, $email, $passwordHash, $role]);

        return [
            'success' => true,
            'message' => 'User created successfully',
            'user_id' => $db->lastInsertId()
        ];
    } catch (PDOException $e) {
        return ['success' => false, 'message' => 'Database error: ' . $e->getMessage()];
    }
}
?>