<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Automation Map — Cartographer</title>
    <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
    <style>
        *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
        body {
            font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif;
            background: #f3f4f6; color: #1f2937; min-height: 100vh;
            display: flex; flex-direction: column;
        }
        .toolbar {
            background: #ffffff; border-bottom: 1px solid #e5e7eb;
            padding: 12px 20px; display: flex; flex-wrap: wrap; gap: 12px;
            align-items: center; box-shadow: 0 1px 3px rgba(0,0,0,0.06);
        }
        .toolbar label { font-size: 13px; font-weight: 600; color: #6b7280; margin-right: 4px; }
        .toolbar select, .toolbar button {
            font-family: inherit; font-size: 13px; padding: 6px 12px;
            border-radius: 6px; border: 1px solid #d1d5db; background: #fff; color: #1f2937;
        }
        .toolbar select:focus, .toolbar button:focus { outline: none; border-color: #2563eb; box-shadow: 0 0 0 2px rgba(37,99,235,0.15); }
        .toolbar-group { display: flex; align-items: center; gap: 4px; }
        .toolbar-separator { width: 1px; height: 24px; background: #e5e7eb; }
        .btn-primary { background: #2563eb; color: #fff; border: none; cursor: pointer; font-weight: 600; }
        .btn-primary:hover { background: #1d4ed8; }
        .btn-primary:disabled { background: #93c5fd; cursor: not-allowed; }
        .btn-secondary { background: #f3f4f6; color: #374151; cursor: pointer; font-weight: 500; }
        .btn-secondary:hover { background: #e5e7eb; }
        .btn-secondary:disabled { color: #9ca3af; cursor: not-allowed; }
        .content { flex: 1; padding: 16px 20px; display: flex; flex-direction: column; }
        .diagram-card {
            background: #fff; border-radius: 10px; border: 1px solid #e5e7eb;
            flex: 1; min-height: 400px; padding: 24px;
            box-shadow: 0 1px 3px rgba(0,0,0,0.04); overflow: auto;
        }
        .diagram-card svg { max-width: 100%; height: auto; }
        .message { text-align: center; padding: 60px 20px; color: #6b7280; }
        .message h2 { font-size: 18px; font-weight: 600; color: #374151; margin: 12px 0 8px; }
        .message p { font-size: 14px; }
        .message-icon { font-size: 48px; }
        .error-banner {
            background: #fef2f2; color: #991b1b; padding: 10px 20px;
            font-size: 13px; display: none; border-bottom: 1px solid #fecaca;
        }
        .loading { text-align: center; padding: 60px; }
        .spinner {
            display: inline-block; width: 32px; height: 32px;
            border: 3px solid #e5e7eb; border-top-color: #2563eb;
            border-radius: 50%; animation: spin 0.7s linear infinite;
        }
        @keyframes spin { to { transform: rotate(360deg); } }
        .loading-text { margin-top: 12px; font-size: 14px; color: #6b7280; }
        .legend { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 12px; padding: 8px 0; font-size: 12px; color: #6b7280; }
        .legend-item { display: flex; align-items: center; gap: 4px; }
        .legend-dot { width: 10px; height: 10px; border-radius: 50%; }
        .node { cursor: pointer !important; }
    </style>
</head>
<body>
    <div class="toolbar">
        <div class="toolbar-group">
            <label for="entitySelect">Table:</label>
            <select id="entitySelect" disabled>
                <option value="">Loading tables...</option>
            </select>
        </div>
        <div class="toolbar-separator"></div>
        <button id="btnDownload" class="btn-secondary" disabled>Download as PNG</button>
    </div>

    <div id="errorBanner" class="error-banner"></div>

    <div class="content">
        <div class="diagram-card" id="diagramContainer">
            <div class="message" id="placeholderMessage">
                <div class="message-icon">&#9889;</div>
                <h2>Automation Map</h2>
                <p>Select a table to see all automations — plugins, cloud flows, business rules, and workflows — that fire on it.</p>
            </div>
        </div>
        <div class="legend" id="legend" style="display:none;">
            <div class="legend-item"><div class="legend-dot" style="background:#3b82f6;"></div> Plugin Step</div>
            <div class="legend-item"><div class="legend-dot" style="background:#8b5cf6;"></div> Cloud Flow</div>
            <div class="legend-item"><div class="legend-dot" style="background:#f59e0b;"></div> Business Rule</div>
            <div class="legend-item"><div class="legend-dot" style="background:#6b7280;"></div> Classic Workflow</div>
            <div class="legend-item"><div class="legend-dot" style="background:#10b981;"></div> Custom Action / BPF</div>
        </div>
    </div>

    <!-- Tier Gate Overlay -->
    <div id="tierGate" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:#f3f4f6;z-index:1000;align-items:center;justify-content:center;">
        <div style="text-align:center;max-width:400px;padding:40px;">
            <div style="font-size:48px;margin-bottom:16px;">&#128274;</div>
            <h2 style="font-size:20px;font-weight:700;color:#1f2937;margin-bottom:8px;">Automation Map requires Professional or Enterprise</h2>
            <p style="font-size:14px;color:#6b7280;margin-bottom:20px;">Upgrade your plan to visualize all automations — plugins, cloud flows, business rules, and workflows — firing on each table.</p>
            <a href="https://www.verseblocks.com/pricing" target="_blank" style="display:inline-block;padding:10px 24px;background:#2563eb;color:white;border-radius:8px;text-decoration:none;font-size:14px;font-weight:600;">View Plans &amp; Upgrade</a>
        </div>
    </div>

    <script>
        // Analytics - fire and forget
        function vbAnalytics(eventType, details) {
            try {
                fetch('/api/data/v9.2/vb_configurations?$select=vb_license_key,vb_org_id&$top=1', {
                    headers: { 'Accept': 'application/json', 'OData-MaxVersion': '4.0', 'OData-Version': '4.0' },
                    credentials: 'include'
                }).then(function(r) { return r.json(); }).then(function(d) {
                    if (!d.value || !d.value[0]) return;
                    var c = d.value[0];
                    fetch('https://cartographer-api.azurewebsites.net/api/analytics/event', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({
                            licenseKey: c.vb_license_key,
                            orgId: c.vb_org_id || '',
                            eventType: eventType,
                            details: JSON.stringify(details || {}),
                            timestamp: new Date().toISOString()
                        })
                    });
                });
            } catch(e) { /* silent */ }
        }
        vbAnalytics('page_view', {page: 'automation_map'});

        (function () {
            'use strict';

            var API_BASE = '/api/data/v9.2/';
            var diagramContainer = document.getElementById('diagramContainer');
            var entitySelect = document.getElementById('entitySelect');
            var btnDownload = document.getElementById('btnDownload');
            var errorBanner = document.getElementById('errorBanner');
            var legend = document.getElementById('legend');
            var currentDiagramSvg = null;
            var componentMap = {};

            // Automation component types only
            var AUTOMATION_TYPES = [10001001,10001002,10001003,10001004,10001005,10001006,10001007,10001023];
            var TYPE_LABELS = {
                10001001: 'Plugin Step', 10001002: 'Cloud Flow (Auto)', 10001003: 'Cloud Flow (Sched)',
                10001004: 'Cloud Flow (Instant)', 10001005: 'Business Rule', 10001006: 'Classic Workflow',
                10001007: 'Custom Action', 10001023: 'BPF'
            };
            var TYPE_COLORS = {
                10001001: '#3b82f6', 10001002: '#8b5cf6', 10001003: '#8b5cf6',
                10001004: '#8b5cf6', 10001005: '#f59e0b', 10001006: '#6b7280',
                10001007: '#10b981', 10001023: '#10b981'
            };

            // Trigger event choice values
            var TRIGGER_LABELS = {
                10001000: 'Create', 10001001: 'Update', 10001002: 'Delete',
                10001003: 'Retrieve', 10001004: 'Associate', 10001005: 'SetState',
                10001006: 'On-Demand', 10001007: 'Scheduled', 10001008: 'Other'
            };

            var STAGE_LABELS = {
                10001000: 'Pre-Validation', 10001001: 'Pre-Operation',
                10001002: 'Post-Operation', 10001003: 'Async', 10001004: ''
            };

            mermaid.initialize({
                startOnLoad: false, theme: 'default', securityLevel: 'loose',
                flowchart: { useMaxWidth: true, htmlLabels: true, curve: 'basis' }
            });

            function apiGet(url) {
                return fetch(API_BASE + url, {
                    headers: { 'Accept': 'application/json', 'OData-MaxVersion': '4.0', 'OData-Version': '4.0',
                               'Prefer': 'odata.include-annotations="*",odata.maxpagesize=500' },
                    credentials: 'include'
                }).then(function (r) {
                    if (!r.ok) return r.text().then(function (t) { throw new Error('API error (' + r.status + '): ' + t); });
                    return r.json();
                });
            }

            function showError(msg) { errorBanner.textContent = msg; errorBanner.style.display = 'block'; }
            function clearError() { errorBanner.style.display = 'none'; }
            function showLoading(msg) {
                diagramContainer.innerHTML = '<div class="loading"><div class="spinner"></div><div class="loading-text">' + (msg || 'Loading...') + '</div></div>';
                currentDiagramSvg = null; legend.style.display = 'none';
            }
            function showMessage(title, body) {
                diagramContainer.innerHTML = '<div class="message"><div class="message-icon">&#128196;</div><h2>' + title + '</h2><p>' + body + '</p></div>';
                currentDiagramSvg = null; btnDownload.disabled = true; legend.style.display = 'none';
            }
            function htmlEncode(s) { var d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
            function sanitizeMermaidId(s) { return s.replace(/[^a-zA-Z0-9_]/g, '_').substring(0, 40); }

            // Clean up plugin step names — strip namespaces, add spaces before capitals
            function friendlyName(rawName) {
                if (!rawName) return 'Unknown';
                var name = rawName;
                // Strip everything before the last colon (message info) e.g. "Namespace.Plugin: Create of entity"
                var colonIdx = name.indexOf(':');
                if (colonIdx > 0) {
                    var afterColon = name.substring(colonIdx + 1).trim();
                    var beforeColon = name.substring(0, colonIdx);
                    // Get just the class name from the namespace
                    var parts = beforeColon.split('.');
                    var className = parts[parts.length - 1];
                    // Add spaces before capitals: "OpportunityClose" -> "Opportunity Close"
                    className = className.replace(/([a-z])([A-Z])/g, '$1 $2');
                    name = className + ': ' + afterColon;
                } else {
                    // No colon — try stripping namespace dots
                    var dotParts = name.split('.');
                    if (dotParts.length > 2) {
                        // Take last 2 segments: "Microsoft.Dynamics.Sales.OpportunityRevenue" -> "Sales.Opportunity Revenue"
                        var last = dotParts[dotParts.length - 1].replace(/([a-z])([A-Z])/g, '$1 $2');
                        var secondLast = dotParts[dotParts.length - 2];
                        name = secondLast + ' — ' + last;
                    }
                }
                return name;
            }

            // --- Load all entity definitions from the environment ---
            function loadEntities() {
                entitySelect.innerHTML = '<option value="">Loading tables...</option>';
                entitySelect.disabled = true;

                apiGet("EntityDefinitions?$select=LogicalName,DisplayName,IsIntersect,IsCustomizable").then(function (data) {
                    var entities = (data.value || []).filter(function (e) {
                        // Filter to non-intersect entities with a display name
                        if (e.IsIntersect) return false;
                        var label = e.DisplayName && e.DisplayName.UserLocalizedLabel;
                        return label && label.Label;
                    }).sort(function (a, b) {
                        var la = a.DisplayName.UserLocalizedLabel.Label;
                        var lb = b.DisplayName.UserLocalizedLabel.Label;
                        return la.localeCompare(lb);
                    });

                    entitySelect.innerHTML = '<option value="">-- Select Table (' + entities.length + ') --</option>';
                    entities.forEach(function (e) {
                        var label = e.DisplayName.UserLocalizedLabel.Label;
                        var opt = document.createElement('option');
                        opt.value = e.LogicalName;
                        opt.textContent = label + ' (' + e.LogicalName + ')';
                        entitySelect.appendChild(opt);
                    });
                    entitySelect.disabled = false;
                }).catch(function (err) {
                    showError('Failed to load tables: ' + err.message);
                    entitySelect.innerHTML = '<option value="">Error loading tables</option>';
                });
            }

            // --- Load automations from LIVE system tables (not crawled data) ---
            function loadAutomationsForEntity(entityLogicalName) {
                clearError();
                showLoading('Loading automations for ' + entityLogicalName + '...');
                btnDownload.disabled = true;
                componentMap = {};

                // Query 3 sources in parallel:
                // 1. Plugin steps (sdkmessageprocessingstep) via filter on primary entity
                // 2. Business rules (workflow category=2)
                // 3. Workflows & cloud flows (workflow category=0,5)
                var pluginQuery = "sdkmessageprocessingsteps?$select=name,stage,mode,sdkmessageprocessingstepid,rank" +
                    "&$expand=sdkmessageid($select=name)" +
                    "&$filter=sdkmessagefilterid/primaryobjecttypecode eq '" + entityLogicalName + "' and ishidden/Value eq false and statecode eq 0";

                var brQuery = "workflows?$select=name,workflowid,primaryentity,category,statuscode,statecode" +
                    "&$filter=primaryentity eq '" + entityLogicalName + "' and category eq 2 and statecode eq 1";

                var flowQuery = "workflows?$select=name,workflowid,primaryentity,category,statuscode,statecode,type" +
                    "&$filter=primaryentity eq '" + entityLogicalName + "' and (category eq 0 or category eq 5) and statecode eq 1";

                // Also query crawled components for AI summaries
                var crawledQuery = "vb_components?$select=vb_source_id,vb_ai_summary,vb_componentid" +
                    "&$filter=vb_entity_logical_name eq '" + entityLogicalName + "' and vb_ai_summary ne null";

                Promise.all([
                    apiGet(pluginQuery).catch(function () { return { value: [] }; }),
                    apiGet(brQuery).catch(function () { return { value: [] }; }),
                    apiGet(flowQuery).catch(function () { return { value: [] }; }),
                    apiGet(crawledQuery).catch(function () { return { value: [] }; })
                ]).then(function (results) {
                    // Build a lookup of source_id -> ai_summary from crawled data
                    var summaryMap = {};
                    (results[3].value || []).forEach(function (c) {
                        if (c.vb_source_id && c.vb_ai_summary) {
                            summaryMap[c.vb_source_id.toLowerCase()] = c.vb_ai_summary;
                        }
                        if (c.vb_componentid) {
                            componentMap[c.vb_source_id] = c.vb_componentid;
                        }
                    });
                    var components = [];

                    // Process plugin steps
                    (results[0].value || []).forEach(function (step) {
                        var msgName = step.sdkmessageid ? step.sdkmessageid.name : 'Unknown';
                        var stageVal = step.stage; // 10=PreValidation, 20=PreOperation, 40=PostOperation
                        var stageMap = { 10: 10001000, 20: 10001001, 40: 10001002 };
                        var triggerMap = { 'Create': 10001000, 'Update': 10001001, 'Delete': 10001002,
                                          'Retrieve': 10001003, 'Associate': 10001004, 'SetState': 10001005,
                                          'SetStateDynamicEntity': 10001005 };

                        var stepId = step.sdkmessageprocessingstepid;
                        components.push({
                            name: friendlyName(step.name || 'Plugin Step'),
                            type: 10001001,
                            trigger: triggerMap[msgName] !== undefined ? triggerMap[msgName] : 10001008,
                            stage: stageMap[stageVal] || 10001004,
                            id: stepId,
                            isAsync: step.mode === 1,
                            summary: summaryMap[stepId ? stepId.toLowerCase() : ''] || ''
                        });
                    });

                    // Process business rules
                    (results[1].value || []).forEach(function (br) {
                        var brId = br.workflowid;
                        components.push({
                            name: br.name || 'Business Rule',
                            type: 10001005,
                            trigger: 10001008,
                            stage: 10001004,
                            id: brId,
                            summary: summaryMap[brId ? brId.toLowerCase() : ''] || ''
                        });
                    });

                    // Process workflows and cloud flows
                    (results[2].value || []).forEach(function (wf) {
                        var compType;
                        if (wf.category === 0) {
                            compType = 10001006; // Classic Workflow
                        } else if (wf.category === 5) {
                            compType = 10001002; // Cloud Flow (default to Automated)
                        } else {
                            compType = 10001006;
                        }
                        var wfId = wf.workflowid;
                        components.push({
                            name: wf.name || 'Workflow',
                            type: compType,
                            trigger: 10001008,
                            stage: 10001003,
                            summary: summaryMap[wfId ? wfId.toLowerCase() : ''] || '',
                            id: wf.workflowid
                        });
                    });

                    if (components.length === 0) {
                        showMessage('No Automations Found',
                            'No active plugins, cloud flows, business rules, or workflows are registered on <strong>' +
                            htmlEncode(entityLogicalName) + '</strong>.');
                        return;
                    }

                    buildAndRenderDiagram(entityLogicalName, components);
                }).catch(function (err) {
                    showError('Failed to load automations: ' + err.message);
                    showMessage('Error', 'Could not load automation data.');
                });
            }

            // --- Build Mermaid diagram client-side ---
            function buildAndRenderDiagram(entityLogicalName, components) {
                // Group by trigger event
                var groups = {};
                components.forEach(function (c) {
                    var eventLabel = TRIGGER_LABELS[c.trigger] || 'Other';
                    if (!groups[eventLabel]) groups[eventLabel] = [];
                    groups[eventLabel].push(c);
                });

                // Build Mermaid flowchart
                var lines = ['flowchart LR'];
                lines.push('');

                // Entity node in center
                var entityId = sanitizeMermaidId(entityLogicalName);
                lines.push('    ' + entityId + '[("' + entityLogicalName + '")]');
                lines.push('    style ' + entityId + ' fill:#1e40af,stroke:#1e3a8a,color:#fff,stroke-width:2px');
                lines.push('');

                var nodeIndex = 0;
                var eventOrder = ['Create', 'Update', 'Delete', 'Retrieve', 'Associate', 'SetState', 'On-Demand', 'Scheduled', 'Other'];

                eventOrder.forEach(function (eventLabel) {
                    var comps = groups[eventLabel];
                    if (!comps || comps.length === 0) return;

                    var subId = 'event_' + sanitizeMermaidId(eventLabel);
                    lines.push('    subgraph ' + subId + '["' + eventLabel + '"]');

                    comps.forEach(function (comp) {
                        nodeIndex++;
                        var nodeId = 'n' + nodeIndex;
                        var typeLabel = TYPE_LABELS[comp.type] || 'Unknown';
                        var stageLabel = STAGE_LABELS[comp.stage] || '';
                        if (comp.isAsync) stageLabel = 'Async';
                        var label = comp.name.replace(/"/g, "'").replace(/[<>]/g, '').replace(/[#]/g, '');
                        if (label.length > 45) label = label.substring(0, 42) + '...';

                        var nodeLabel = '<b>' + label + '</b><br/><i>' + typeLabel + '</i>';
                        if (stageLabel) nodeLabel += ' · ' + stageLabel;

                        // Add AI summary snippet if available
                        if (comp.summary) {
                            var snippet = comp.summary.replace(/"/g, "'").replace(/[<>#]/g, '');
                            if (snippet.length > 60) snippet = snippet.substring(0, 57) + '...';
                            nodeLabel += '<br/><small style="opacity:0.85">' + snippet + '</small>';
                        }

                        lines.push('        ' + nodeId + '["' + nodeLabel + '"]');

                        var color = TYPE_COLORS[comp.type] || '#6b7280';
                        lines.push('        style ' + nodeId + ' fill:' + color + ',stroke:' + color + ',color:#fff,stroke-width:1px');
                    });

                    lines.push('    end');
                    lines.push('    ' + entityId + ' --> ' + subId);
                    lines.push('');
                });

                var mermaidDef = lines.join('\n');
                renderMermaid(mermaidDef);
            }

            // --- Render Mermaid ---
            function renderMermaid(definition) {
                var id = 'mermaid-' + Date.now();
                mermaid.render(id, definition).then(function (result) {
                    diagramContainer.innerHTML = result.svg;
                    currentDiagramSvg = result.svg;
                    btnDownload.disabled = false;
                    legend.style.display = 'flex';
                    attachNodeClickHandlers();
                }).catch(function (err) {
                    showError('Mermaid rendering error: ' + err.message);
                    showMessage('Rendering Error', 'The diagram could not be rendered.');
                });
            }

            function attachNodeClickHandlers() {
                diagramContainer.querySelectorAll('.node').forEach(function (node) {
                    node.style.cursor = 'pointer';
                    node.addEventListener('click', function () {
                        var nodeId = node.id ? node.id.replace(/^flowchart-/, '').split('-')[0] : '';
                        var compId = componentMap[nodeId];
                        if (compId) openComponentRecord(compId);
                    });
                });
            }

            function openComponentRecord(componentId) {
                try {
                    if (parent && parent.Xrm && parent.Xrm.Navigation) {
                        parent.Xrm.Navigation.openForm({ entityName: 'vb_component', entityId: componentId });
                        return;
                    }
                } catch (e) {}
                window.open(window.location.origin + '/main.aspx?etn=vb_component&id=' + componentId + '&pagetype=entityrecord', '_blank');
            }

            // --- Download as PNG ---
            btnDownload.addEventListener('click', function () {
                if (!currentDiagramSvg) return;
                var svgEl = diagramContainer.querySelector('svg');
                if (!svgEl) return;

                var bbox = svgEl.getBoundingClientRect();
                var scale = 2;
                var canvas = document.createElement('canvas');
                canvas.width = bbox.width * scale;
                canvas.height = bbox.height * scale;
                var ctx = canvas.getContext('2d');
                ctx.scale(scale, scale);

                var svgData = new XMLSerializer().serializeToString(svgEl);
                var svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
                var url = URL.createObjectURL(svgBlob);
                var img = new Image();
                img.onload = function () {
                    ctx.fillStyle = '#fff';
                    ctx.fillRect(0, 0, canvas.width, canvas.height);
                    ctx.drawImage(img, 0, 0, bbox.width, bbox.height);
                    URL.revokeObjectURL(url);
                    canvas.toBlob(function (blob) {
                        var a = document.createElement('a');
                        a.href = URL.createObjectURL(blob);
                        a.download = (entitySelect.value || 'automation-map') + '.png';
                        document.body.appendChild(a); a.click(); document.body.removeChild(a);
                    }, 'image/png');
                };
                img.src = url;
            });

            // --- Entity select change ---
            entitySelect.addEventListener('change', function () {
                if (entitySelect.value) {
                    loadAutomationsForEntity(entitySelect.value);
                } else {
                    showMessage('Automation Map', 'Select a table to see all automations.');
                }
            });

            // --- Init: check tier first ---
            apiGet("vb_configurations?$select=vb_license_tier&$top=1").then(function (data) {
                var tier = data.value && data.value[0] ? data.value[0].vb_license_tier : null;
                // 10001000 = Free (first tier) — block access
                if (tier !== 10001001 && tier !== 10001002) { // Not Professional or Enterprise
                    document.getElementById('tierGate').style.display = 'flex';
                    vbAnalytics('feature_gate_hit', {feature: 'automation_map', tier: 'free'});
                    return;
                }
                loadEntities();
            }).catch(function () {
                loadEntities(); // If config can't be read, allow access (fail open for dev)
            });
        })();
    </script>
</body>
</html>
