<?php
declare(strict_types=1);

require_once dirname(__DIR__) . '/includes/layout.php';
require_once dirname(__DIR__) . '/includes/report-data.php';

crm_page_start('Lead Map', 'reports', 'Geographic lead coverage and heat map');

$db = crm_report_db();
$filters = crm_report_filters();
$selectedStatus = filter_input(INPUT_GET, 'status', FILTER_VALIDATE_INT);
$selectedStatus = $selectedStatus === false || $selectedStatus === null ? 0 : max(0, $selectedStatus);
$points = [];
$statuses = [];
$totalLeads = 0;
$mappedLeads = 0;
$exactLeads = 0;
$approximateLeads = 0;
$mapError = '';

try {
    if (!$db) {
        throw new RuntimeException('ABOX database connection is unavailable.');
    }
    foreach (['jos_crm_leadentry', 'jos_crm_mcustomers', 'jos_crm_mleadstatus'] as $table) {
        if (!crm_report_table($db, $table)) {
            throw new RuntimeException("Required CRM table {$table} is unavailable.");
        }
    }

    $statuses = crm_report_query(
        $db,
        'SELECT id,name FROM jos_crm_mleadstatus ORDER BY orderid,id'
    );
    $where = crm_report_where($db, 'a.date', $filters);
    if ($selectedStatus > 0) {
        $where .= ' AND a.leadstatus=' . $selectedStatus;
    }

    $totalLeads = (int)crm_report_value(
        $db,
        "SELECT COUNT(*) value FROM jos_crm_leadentry a WHERE {$where}"
    );
    $rows = crm_report_query(
        $db,
        "SELECT a.id,a.date,a.leadstatus,
                COALESCE(NULLIF(c.companyname,''),CONCAT('Customer #',a.custid)) company,
                COALESCE(NULLIF(st.name,''),CONCAT('Status #',a.leadstatus)) status,
                a.latitude lead_latitude,a.longitude lead_longitude,
                c.latitude customer_latitude,c.longitude customer_longitude,
                COALESCE(c.localityid,0) localityid,COALESCE(c.talukaid,0) talukaid,
                COALESCE(c.districtid,0) districtid,COALESCE(c.stateid,0) stateid,
                COALESCE(NULLIF(loc.name,''),'') locality_name,
                COALESCE(NULLIF(tal.name,''),'') taluka_name,
                COALESCE(NULLIF(dist.name,''),'') district_name,
                COALESCE(NULLIF(state.name,''),'') state_name
         FROM jos_crm_leadentry a
         LEFT JOIN jos_crm_mcustomers c ON c.id=a.custid
         LEFT JOIN jos_crm_mleadstatus st ON st.id=a.leadstatus
         LEFT JOIN jos_crm_locality loc ON loc.id=c.localityid
         LEFT JOIN jos_crm_mcitys tal ON tal.id=c.talukaid
         LEFT JOIN jos_crm_district dist ON dist.id=c.districtid
         LEFT JOIN jos_crm_mstates state ON state.id=c.stateid
         WHERE {$where}
         ORDER BY a.date DESC,a.id DESC"
    );

    $validCoordinates = static function ($latitude, $longitude): bool {
        if (!is_numeric($latitude) || !is_numeric($longitude)) {
            return false;
        }
        $latitude = (float)$latitude;
        $longitude = (float)$longitude;
        return $latitude >= -90 && $latitude <= 90 && $longitude >= -180 && $longitude <= 180
            && abs($latitude) > 0.000001 && abs($longitude) > 0.000001;
    };

    /*
     * Build geographic centroids from customers that already have trustworthy coordinates.
     * Missing leads then fall back through locality -> taluka -> district -> state.
     */
    $centroidBuckets = ['locality' => [], 'taluka' => [], 'district' => [], 'state' => []];
    $customerLocations = crm_report_query(
        $db,
        "SELECT latitude,longitude,COALESCE(localityid,0) localityid,COALESCE(talukaid,0) talukaid,
                COALESCE(districtid,0) districtid,COALESCE(stateid,0) stateid
         FROM jos_crm_mcustomers
         WHERE latitude IS NOT NULL AND longitude IS NOT NULL
           AND TRIM(latitude) NOT IN ('','0','0.0')
           AND TRIM(longitude) NOT IN ('','0','0.0')"
    );
    foreach ($customerLocations as $customerLocation) {
        if (!$validCoordinates($customerLocation['latitude'], $customerLocation['longitude'])) {
            continue;
        }
        foreach (['locality', 'taluka', 'district', 'state'] as $level) {
            $id = (int)$customerLocation[$level . 'id'];
            if ($id <= 0) {
                continue;
            }
            if (!isset($centroidBuckets[$level][$id])) {
                $centroidBuckets[$level][$id] = ['lat' => 0.0, 'lng' => 0.0, 'count' => 0];
            }
            $centroidBuckets[$level][$id]['lat'] += (float)$customerLocation['latitude'];
            $centroidBuckets[$level][$id]['lng'] += (float)$customerLocation['longitude'];
            $centroidBuckets[$level][$id]['count']++;
        }
    }
    foreach ($centroidBuckets as $level => $buckets) {
        foreach ($buckets as $id => $bucket) {
            $centroidBuckets[$level][$id] = [
                'lat' => $bucket['lat'] / $bucket['count'],
                'lng' => $bucket['lng'] / $bucket['count'],
            ];
        }
    }

    foreach ($rows as $row) {
        $latitude = null;
        $longitude = null;
        $approximate = false;
        $precision = 'Exact coordinates';

        if ($validCoordinates($row['lead_latitude'], $row['lead_longitude'])) {
            $latitude = (float)$row['lead_latitude'];
            $longitude = (float)$row['lead_longitude'];
        } elseif ($validCoordinates($row['customer_latitude'], $row['customer_longitude'])) {
            $latitude = (float)$row['customer_latitude'];
            $longitude = (float)$row['customer_longitude'];
        } else {
            foreach (['locality', 'taluka', 'district', 'state'] as $level) {
                $id = (int)$row[$level . 'id'];
                if ($id > 0 && isset($centroidBuckets[$level][$id])) {
                    $latitude = $centroidBuckets[$level][$id]['lat'];
                    $longitude = $centroidBuckets[$level][$id]['lng'];
                    $approximate = true;
                    $label = trim((string)$row[$level . '_name']);
                    $precision = 'Approximate ' . ucfirst($level) . ($label !== '' ? ': ' . $label : '');
                    break;
                }
            }
        }
        if ($latitude === null || $longitude === null) {
            continue;
        }
        $points[] = [
            'id' => (int)$row['id'],
            'lat' => $latitude,
            'lng' => $longitude,
            'company' => (string)$row['company'],
            'status' => (string)$row['status'],
            'statusId' => (int)$row['leadstatus'],
            'date' => crm_date($row['date']),
            'approximate' => $approximate,
            'precision' => $precision,
        ];
        $approximate ? $approximateLeads++ : $exactLeads++;
    }
    $mappedLeads = count($points);
} catch (Throwable $error) {
    $mapError = $error->getMessage();
}

$mapPayload = json_encode(
    $points,
    JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
);
?>
<link rel="stylesheet" href="<?= crm_e(crm_url('assets/vendor/leaflet/leaflet.css')) ?>">
<link rel="stylesheet" href="<?= crm_e(crm_url('assets/vendor/leaflet/MarkerCluster.css')) ?>">
<link rel="stylesheet" href="<?= crm_e(crm_url('assets/vendor/leaflet/MarkerCluster.Default.css')) ?>">

<form class="command-bar map-filter-bar" method="get">
    <strong><i class="fa-solid fa-map-location-dot"></i> Map Filters</strong>
    <div class="form-actions">
        <input class="control report-date" name="from" value="<?= crm_e($filters['from']) ?>" type="date" aria-label="From date">
        <input class="control report-date" name="to" value="<?= crm_e($filters['to']) ?>" type="date" aria-label="To date">
        <select class="control map-status-filter" name="status" aria-label="Lead status">
            <option value="0">All statuses</option>
            <?php foreach ($statuses as $status): ?>
                <option value="<?= (int)$status['id'] ?>" <?= $selectedStatus === (int)$status['id'] ? 'selected' : '' ?>>
                    <?= crm_e((string)$status['name']) ?>
                </option>
            <?php endforeach; ?>
        </select>
        <a class="button secondary" href="<?= crm_e(crm_url('reports/lead-map.php')) ?>"><i class="fa-solid fa-rotate"></i> Reset</a>
        <button class="button primary" type="submit"><i class="fa-solid fa-filter"></i> Apply</button>
    </div>
</form>

<?php if ($mapError !== ''): ?>
    <div class="report-notice"><i class="fa-solid fa-triangle-exclamation"></i><span><?= crm_e($mapError) ?></span></div>
<?php elseif ($totalLeads === 0): ?>
    <div class="report-notice"><i class="fa-solid fa-circle-info"></i><span>The map is connected; INV11 is waiting for the migrated lead coordinates.</span></div>
<?php endif; ?>

<section class="map-kpi-grid">
    <article class="card kpi"><span>Exact Locations</span><strong><?= crm_n($exactLeads) ?></strong><small>Saved latitude and longitude</small></article>
    <article class="card kpi"><span>Approximate Locations</span><strong><?= crm_n($approximateLeads) ?></strong><small>Locality, taluka, district or state</small></article>
    <article class="card kpi"><span>Still Unmapped</span><strong><?= crm_n(max(0, $totalLeads - $mappedLeads)) ?></strong><small>No usable location reference</small></article>
    <article class="card kpi"><span>Map Coverage</span><strong><?= crm_pct($mappedLeads, $totalLeads) ?></strong><small><?= crm_n($mappedLeads) ?> of <?= crm_n($totalLeads) ?> leads</small></article>
</section>

<section class="card lead-map-card">
    <div class="map-toolbar">
        <div>
            <h2><i class="fa-solid fa-earth-asia"></i> Lead Geography</h2>
            <p>Switch between clustered locations and lead-density heat view.</p>
        </div>
        <div class="map-view-switch" role="group" aria-label="Map view">
            <button class="button primary is-active" id="markerViewButton" type="button"><i class="fa-solid fa-location-dot"></i> Markers</button>
            <button class="button secondary" id="heatViewButton" type="button"><i class="fa-solid fa-fire-flame-curved"></i> Heat Map</button>
            <button class="button secondary" id="fitMapButton" type="button"><i class="fa-solid fa-expand"></i> Fit All</button>
        </div>
    </div>
    <div id="leadMap" class="lead-map" aria-label="CRM lead map"></div>
    <div class="map-legend">
        <span><i class="legend-marker legend-exact"></i> Exact coordinates</span>
        <span><i class="legend-marker legend-approximate"></i> Approximate area</span>
        <span><i class="legend-dot legend-low"></i> Low density</span>
        <span><i class="legend-dot legend-medium"></i> Medium density</span>
        <span><i class="legend-dot legend-high"></i> High density</span>
        <small>Approximate markers use the nearest available locality, taluka, district or state centroid.</small>
    </div>
</section>

<script src="<?= crm_e(crm_url('assets/vendor/leaflet/leaflet.js')) ?>"></script>
<script src="<?= crm_e(crm_url('assets/vendor/leaflet/leaflet.markercluster.js')) ?>"></script>
<script src="<?= crm_e(crm_url('assets/vendor/leaflet/leaflet-heat.js')) ?>"></script>
<script>
(() => {
    const leads = <?= $mapPayload ?: '[]' ?>;
    const mapElement = document.getElementById('leadMap');
    const markerButton = document.getElementById('markerViewButton');
    const heatButton = document.getElementById('heatViewButton');
    const fitButton = document.getElementById('fitMapButton');

    if (!mapElement || typeof L === 'undefined') {
        if (mapElement) {
            mapElement.innerHTML = '<div class="map-load-error">The map library could not be loaded.</div>';
        }
        return;
    }

    const map = L.map(mapElement, {zoomControl: true}).setView([20.5937, 78.9629], 5);
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        maxZoom: 19,
        attribution: '&copy; OpenStreetMap contributors'
    }).addTo(map);

    const markerLayer = L.markerClusterGroup({
        chunkedLoading: true,
        maxClusterRadius: 55,
        showCoverageOnHover: false
    });
    const heatPoints = [];
    const bounds = [];

    const escapeHtml = (value) => String(value)
        .replaceAll('&', '&amp;')
        .replaceAll('<', '&lt;')
        .replaceAll('>', '&gt;')
        .replaceAll('"', '&quot;')
        .replaceAll("'", '&#039;');

    leads.forEach((lead) => {
        const location = [Number(lead.lat), Number(lead.lng)];
        bounds.push(location);
        heatPoints.push([location[0], location[1], lead.approximate ? 0.35 : 0.7]);
        const marker = lead.approximate
            ? L.circleMarker(location, {
                radius: 7,
                color: '#d97706',
                fillColor: '#fbbf24',
                fillOpacity: 0.78,
                weight: 2
            })
            : L.marker(location);
        marker
            .bindPopup(
                `<div class="lead-map-popup">
                    <strong>${escapeHtml(lead.company)}</strong>
                    <span>LEAD-${escapeHtml(lead.id)}</span>
                    <span>${escapeHtml(lead.status)}</span>
                    <small>${escapeHtml(lead.date)}</small>
                    <small class="${lead.approximate ? 'map-location-approximate' : 'map-location-exact'}">${escapeHtml(lead.precision)}</small>
                </div>`
            )
            .addTo(markerLayer);
    });

    const heatLayer = L.heatLayer(heatPoints, {
        radius: 28,
        blur: 22,
        maxZoom: 16,
        gradient: {0.2: '#2563eb', 0.5: '#22c55e', 0.72: '#f59e0b', 1: '#dc2626'}
    });
    markerLayer.addTo(map);

    const setView = (view) => {
        const heat = view === 'heat';
        if (heat) {
            map.removeLayer(markerLayer);
            heatLayer.addTo(map);
        } else {
            map.removeLayer(heatLayer);
            markerLayer.addTo(map);
        }
        markerButton.classList.toggle('primary', !heat);
        markerButton.classList.toggle('secondary', heat);
        markerButton.classList.toggle('is-active', !heat);
        heatButton.classList.toggle('primary', heat);
        heatButton.classList.toggle('secondary', !heat);
        heatButton.classList.toggle('is-active', heat);
    };

    markerButton.addEventListener('click', () => setView('markers'));
    heatButton.addEventListener('click', () => setView('heat'));
    fitButton.addEventListener('click', () => {
        if (bounds.length) {
            map.fitBounds(bounds, {padding: [35, 35], maxZoom: 14});
        } else {
            map.setView([20.5937, 78.9629], 5);
        }
    });

    if (bounds.length) {
        map.fitBounds(bounds, {padding: [35, 35], maxZoom: 14});
    }
    setTimeout(() => map.invalidateSize(), 100);
})();
</script>
<?php crm_page_end(); ?>
