<?php
if (!defined('ABSPATH')) exit;

// PHP 8.3 (and some configurations of OPcache) can hoist an unconditional
// top-level `class X {}` to compile time. A plain `if (class_exists(X))
// return; class X {}` pattern then fails with "Cannot declare class ...
// already in use" when a sibling plugin re-includes this file via a
// different absolute path. Wrapping the declaration inside an `if` block
// makes it conditional — PHP evaluates at runtime whether to declare.
if (!class_exists('Forge_Suite_Dashboard')) {

class Forge_Suite_Dashboard {
    private static $instance = null;
    private static $menu_registered = false;

    /**
 * Base64-encoded SVG logos of the 4 Forge plugins. Inlined here so the
 * "not installed" card can render the right logo even when that
 * plugin's asset dir doesn't exist on disk. Rendered as data-URI <img>.
 * Source: {plugin}/assets/logo.svg with xml-decl + Adobe comments stripped.
 */
    const LOGOS_B64 = [
        'rankforge' => 'IDxzdmcgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMTA0IDEwNi4wMSI+PCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI5LjguMywgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDIuMS4xIEJ1aWxkIDMpIC0tPjxkZWZzPjxzdHlsZT4gLnN0MCB7IGZpbGw6ICNkZGQ5Yzg7IH0gLnN0MSB7IG9wYWNpdHk6IC42OyB9IC5zdDEsIC5zdDIsIC5zdDMgeyBpc29sYXRpb246IGlzb2xhdGU7IH0gLnN0MSwgLnN0MiwgLnN0MywgLnN0NCB7IGZpbGw6ICNmZjZiMzU7IH0gLnN0MiB7IG9wYWNpdHk6IC40OyB9IC5zdDMgeyBvcGFjaXR5OiAuOTsgfSA8L3N0eWxlPjwvZGVmcz48cGF0aCBjbGFzcz0ic3QwIiBkPSJNMTAxLjIsMTA1LjQ5Yy0uNzEuMzUtMS40Ni41Mi0yLjIuNTItMS44NSwwLTMuNjItMS4wMy00LjQ5LTIuODFMNTIsMTYuMzcsOS40OSwxMDMuMmMtLjg3LDEuNzgtMi42NCwyLjgxLTQuNDksMi44MS0uNzQsMC0xLjQ5LS4xNy0yLjItLjUyLTIuNDgtMS4yMS0zLjUtNC4yLTIuMjktNi42OEw0Ny40MiwzYy4zMS0uNzEuNzktMS4zNiwxLjQxLTEuODYuMDgtLjA3LjE3LS4xNC4yNi0uMi4xLS4wNy4xOS0uMTQuMy0uMi4xLS4wNi4yLS4xMi4zLS4xNy4wMy0uMDIuMDctLjA0LjExLS4wNi4wMywwLC4wNi0uMDMuMDktLjA0LjEyLS4wNi4yNC0uMTEuMzctLjE1LjA1LS4wMy4xMS0uMDUuMTctLjA2LjEyLS4wNS4yNS0uMDguMzctLjExLjE4LS4wNS4zNi0uMDguNTQtLjEuMDYsMCwuMTMtLjAyLjE5LS4wMy4xNCwwLC4yOS0uMDIuNDQtLjAyaC4wNmMuMTUsMCwuMywwLC40NC4wMi4wNiwwLC4xMy4wMi4xOS4wMy4xOC4wMi4zNi4wNi41NC4xLjEyLjAzLjI1LjA2LjM3LjExLjA2LjAyLjExLjA0LjE3LjA2LjEzLjA0LjI1LjA5LjM3LjE1LjA3LjAzLjEzLjA3LjIuMS4xLjA1LjIuMTEuMy4xN3MuMi4xMy4zLjJjLjA5LjA2LjE4LjEzLjI2LjIuNTQuNDQsMSwxLDEuMzIsMS42N2wuMDkuMTksNDYuOTEsOTUuODFjMS4yMiwyLjQ4LjE5LDUuNDctMi4yOSw2LjY4aDBaIi8+PGc+PHBhdGggY2xhc3M9InN0NCIgZD0iTTY5LjQxLDczLjk5bC02LjY2LTYuNjZjMS45My0yLjM1LDMuMDktNS4zNiwzLjA5LTguNjMsMC03LjUxLTYuMTEtMTMuNjItMTMuNjItMTMuNjJzLTEzLjYyLDYuMTItMTMuNjIsMTMuNjIsNi4xMSwxMy42MiwxMy42MiwxMy42MmMzLjI3LDAsNi4yOC0xLjE1LDguNjMtMy4wOGw2LjY2LDYuNjZjLjI2LjI2LjYxLjM5Ljk2LjM5cy42OS0uMTMuOTYtLjM5Yy41My0uNTMuNTMtMS4zOSwwLTEuOTJoLS4wMlpNNTkuOTMsNjcuNDJjLTIuMDYsMS44My00Ljc1LDIuOTMtNy43MSwyLjkzLTYuNDIsMC0xMS42NC01LjIyLTExLjY0LTExLjY0czUuMjItMTEuNjQsMTEuNjQtMTEuNjQsMTEuNjQsNS4yMiwxMS42NCwxMS42NGMwLDIuOTYtMS4xMSw1LjY2LTIuOTMsNy43MS0uMzEuMzUtLjY0LjY5LS45OS45OWgwWiIvPjxnPjxyZWN0IGNsYXNzPSJzdDIiIHg9IjQ1IiB5PSI1OC43MSIgd2lkdGg9IjMuNjEiIGhlaWdodD0iNy4yMiIgcng9IjEiIHJ5PSIxIi8+PHJlY3QgY2xhc3M9InN0MSIgeD0iNTAuNDEiIHk9IjU1LjEiIHdpZHRoPSIzLjYxIiBoZWlnaHQ9IjEwLjgzIiByeD0iMSIgcnk9IjEiLz48cmVjdCBjbGFzcz0ic3QzIiB4PSI1NS44MiIgeT0iNTEuNDkiIHdpZHRoPSIzLjYxIiBoZWlnaHQ9IjE0LjQ0IiByeD0iMSIgcnk9IjEiLz48L2c+PC9nPjwvc3ZnPg==',
        'langforge' => 'IDxzdmcgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMTA0IDEwNi4wMSI+PCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI5LjguMywgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDIuMS4xIEJ1aWxkIDMpIC0tPjxkZWZzPjxzdHlsZT4gLnN0MCB7IGlzb2xhdGlvbjogaXNvbGF0ZTsgfSAuc3QxIHsgZmlsbDogI2RkZDljODsgfSAuc3QyIHsgZmlsbDogIzBlYzQ5ZTsgfSAuc3QzIHsgZmlsbDogIzBjMWEzYTsgfSA8L3N0eWxlPjwvZGVmcz48cGF0aCBjbGFzcz0ic3QxIiBkPSJNMTAxLjIsMTA1LjQ5Yy0uNzEuMzUtMS40Ni41Mi0yLjE5LjUyLTEuODUsMC0zLjYzLTEuMDMtNC41LTIuODFMNTIsMTYuMzcsOS40OSwxMDMuMmMtLjg3LDEuNzgtMi42NCwyLjgxLTQuNDksMi44MS0uNzQsMC0xLjQ5LS4xNy0yLjItLjUyLTIuNDgtMS4yMS0zLjUtNC4yLTIuMjktNi42OEw0Ny40MiwzYy4zMS0uNzEuNzktMS4zNiwxLjQxLTEuODYuMDgtLjA3LjE3LS4xNC4yNi0uMi4xLS4wNy4xOS0uMTQuMy0uMi4xLS4wNi4yLS4xMi4zLS4xNy4wMy0uMDIuMDctLjA0LjExLS4wNi4wMywwLC4wNi0uMDMuMDktLjA0LjEyLS4wNi4yNC0uMTEuMzctLjE1LjA1LS4wMy4xMS0uMDUuMTctLjA2LjEyLS4wNS4yNS0uMDguMzctLjExLjE4LS4wNS4zNi0uMDguNTQtLjEuMDYsMCwuMTMtLjAyLjE5LS4wMy4xNCwwLC4yOS0uMDIuNDQtLjAyaC4wNmMuMTUsMCwuMywwLC40NC4wMi4wNiwwLC4xMy4wMi4xOS4wMy4xOC4wMi4zNi4wNi41NC4xLjEyLjAzLjI1LjA2LjM3LjExLjA2LjAyLjExLjA0LjE3LjA2LjEzLjA0LjI1LjA5LjM3LjE1LjA3LjAzLjEzLjA3LjIuMS4xLjA1LjIuMTEuMy4xN3MuMi4xMy4zLjJjLjA5LjA2LjE4LjEzLjI2LjIuNTQuNDQsMSwxLDEuMzIsMS42N2wuMDkuMTksNDYuOTEsOTUuODFjMS4yMiwyLjQ4LjE5LDUuNDctMi4yOSw2LjY4aDBaIi8+PGc+PGc+PHJlY3QgY2xhc3M9InN0MiIgeD0iMjYuNTEiIHk9IjU1LjE1IiB3aWR0aD0iMTUuMTEiIGhlaWdodD0iMTciIHJ4PSI0IiByeT0iNCIvPjxnIGNsYXNzPSJzdDAiPjxwYXRoIGNsYXNzPSJzdDMiIGQ9Ik0zNy40Myw2Ni45N2gtMi43MnYtLjMzYy4yNC0uMDIuNDQtLjA1LjYtLjA5cy4yNC0uMS4yNC0uMTd2LS4xcy0uMDItLjA4LS4wMy0uMTFsLS41NC0xLjQ3aC0yLjNjLS4wOS4yMS0uMTYuNC0uMjEuNTZzLS4xLjMxLS4xNS40NGMtLjA0LjEzLS4wNy4yNC0uMDguMzItLjAyLjA4LS4wMi4xNS0uMDIuMiwwLC4xMi4xLjIyLjI5LjI5cy40MS4xMS42NS4xMnYuMzNoLTIuNDZ2LS4zM2MuMDgsMCwuMTgtLjAyLjMtLjA1LjEyLS4wMy4yMi0uMDcuMy0uMTIuMTItLjA4LjIyLS4xNy4yOS0uMjYuMDctLjA5LjEzLS4yMi4yLS4zOC4zMy0uODIuNjktMS43NCwxLjA5LTIuNzdzLjc1LTEuOTQsMS4wNi0yLjczaC4zN2wyLjE4LDUuNjRjLjA1LjEyLjEuMjIuMTYuMjlzLjE0LjE1LjI0LjIyYy4wNy4wNC4xNi4wOC4yOC4xMS4xMS4wMy4yMS4wNS4yOS4wNXYuMzNoLS4wM1pNMzQuOCw2NC4yOWwtLjk5LTIuNTQtLjk4LDIuNTRoMS45N1oiLz48L2c+PC9nPjxnPjxyZWN0IGNsYXNzPSJzdDIiIHg9IjQ0LjQ1IiB5PSI1NS4xNSIgd2lkdGg9IjE1LjExIiBoZWlnaHQ9IjE3IiByeD0iNCIgcnk9IjQiLz48ZyBjbGFzcz0ic3QwIj48cGF0aCBjbGFzcz0ic3QzIiBkPSJNNTMuNzMsNjIuMzhjLS4wMy4xMi0uMDYuMjUtLjA5LjM2LDEuNjYuMjksMi4yOCwxLjMzLDIuMjgsMi40NSwwLDEuNS0xLjI4LDIuNDEtMy42NCwyLjY3LS4wNC0uMTMtLjE3LS40Mi0uMjUtLjU3LDEuOTUtLjE5LDMuMjEtLjg3LDMuMjEtMi4wOXYtLjA3Yy0uMDUtMS4xLS43OC0xLjYzLTEuNzktMS44LS40MSwxLjA3LTEuMSwyLjA2LTEuODgsMi43OC4wNy4yLjE1LjM3LjI1LjUxbC0uNTYuMzVjLS4wOC0uMTMtLjE2LS4yOS0uMjMtLjQ3LS43Ni41Ni0xLjM2LjcxLTEuNzguNzEtLjc2LDAtMS4xNC0uNTYtMS4xNC0xLjI2LDAtMS4zLDEuMDgtMi4zNCwyLjQ1LTIuODktLjAyLS40OS0uMDItMS4wMywwLTEuNTktMS41OSwwLTEuNzYsMC0yLjA4LS4wMmwtLjAyLS42MmMuNDguMDMsMS4zLjAzLDIuMTEuMDMsMC0uNDMuMDMtLjk2LjA1LTEuNDFsLjk0LjA3YzAsLjA5LS4xLjE3LS4yNS4xOS0uMDMuMzUtLjA1Ljc4LS4wOCwxLjEzLDEuMDMtLjAzLDIuODMtLjE1LDMuNzktLjRsLjA4LjY0Yy0xLC4yNS0yLjgyLjM0LTMuOS4zNy0uMDIuNDktLjAzLjk2LS4wMiwxLjQxLjY2LS4yMSwxLjI2LS4yMiwxLjQyLS4yMmguMzljLjA0LS4xNS4wNy0uMjkuMDktLjQ0bC42Ny4xN2gwWk01MC44NCw2NS45Yy0uMTUtLjU4LS4yNC0xLjMzLS4yNy0yLjIxLTEuMDMuNDUtMS44MywxLjI0LTEuODMsMi4xNSwwLC41Ni4zMS43NS42NS43My40NCwwLC45NS0uMjYsMS40NS0uNjdaTTUyLjc3LDYzLjI1Yy0uMTUsMC0uODktLjAzLTEuNTkuMjMuMDIuNzYuMDgsMS40Mi4yMiwxLjk1LjU3LS41OCwxLjA4LTEuMzQsMS4zOC0yLjE3aC0uMDFaIi8+PC9nPjwvZz48Zz48cmVjdCBjbGFzcz0ic3QyIiB4PSI2Mi4zOCIgeT0iNTUuMTUiIHdpZHRoPSIxNS4xMSIgaGVpZ2h0PSIxNyIgcng9IjQiIHJ5PSI0Ii8+PGcgY2xhc3M9InN0MCI+PHBhdGggY2xhc3M9InN0MyIgZD0iTTcwLjI0LDYxaDMuNTN2NC40NWgtLjY1di0uNTZoLTIuODh2My4xNGgtLjY2di0zLjE0aC0yLjg0di42aC0uNjN2LTQuNDloMy40N3YtMS43M2wuODUuMDZjMCwuMDctLjA3LjExLS4xOS4xMywwLDAsMCwxLjU0LDAsMS41NFpNNjYuNzQsNjQuMjZoMi44NHYtMi42M2gtMi44NHYyLjYzWk03MC4yNCw2NC4yNmgyLjg4di0yLjYzaC0yLjg4djIuNjNaIi8+PC9nPjwvZz48L2c+PC9zdmc+',
        'formforge' => 'IDxzdmcgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMTA0IDEwNi4wMSI+PCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI5LjguMywgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDIuMS4xIEJ1aWxkIDMpIC0tPjxkZWZzPjxzdHlsZT4gLnN0MCwgLnN0MSB7IGZpbGw6ICMzYjgyZjY7IH0gLnN0MiB7IGZpbGw6ICNkZGQ5Yzg7IH0gLnN0MSB7IGlzb2xhdGlvbjogaXNvbGF0ZTsgb3BhY2l0eTogLjM7IH0gPC9zdHlsZT48L2RlZnM+PHBhdGggY2xhc3M9InN0MiIgZD0iTTEwMS4yLDEwNS40OWMtLjcxLjM1LTEuNDYuNTItMi4yLjUyLTEuODQsMC0zLjYyLTEuMDMtNC40OS0yLjgxTDUyLDE2LjM3LDkuNDksMTAzLjJjLS44NywxLjc4LTIuNjQsMi44MS00LjQ5LDIuODEtLjc0LDAtMS40OS0uMTctMi4yLS41Mi0yLjQ4LTEuMjEtMy41LTQuMi0yLjI5LTYuNjhMNDcuNDIsM2MuMzEtLjcxLjc5LTEuMzYsMS40MS0xLjg2LjA4LS4wNy4xNy0uMTQuMjYtLjIuMS0uMDcuMTktLjE0LjMtLjIuMS0uMDYuMi0uMTIuMy0uMTcuMDMtLjAyLjA3LS4wNC4xMS0uMDYuMDMsMCwuMDYtLjAzLjA5LS4wNC4xMi0uMDYuMjQtLjExLjM3LS4xNS4wNS0uMDMuMTEtLjA1LjE3LS4wNi4xMi0uMDUuMjUtLjA4LjM3LS4xMS4xOC0uMDUuMzYtLjA4LjU0LS4xLjA2LDAsLjEzLS4wMi4xOS0uMDMuMTQsMCwuMjktLjAyLjQ0LS4wMmguMDZjLjE1LDAsLjMsMCwuNDQuMDIuMDYsMCwuMTMuMDIuMTkuMDMuMTguMDIuMzYuMDYuNTQuMS4xMi4wMy4yNS4wNi4zNy4xMS4wNi4wMi4xMS4wNC4xNy4wNi4xMy4wNC4yNS4wOS4zNy4xNS4wNy4wMy4xMy4wNy4yLjEuMS4wNS4yLjExLjMuMTdzLjIuMTMuMy4yYy4wOS4wNi4xOC4xMy4yNi4yLjU0LjQ0LDEsMSwxLjMyLDEuNjdsLjA5LjE5LDQ2LjkxLDk1LjgxYzEuMjIsMi40OC4xOSw1LjQ3LTIuMjksNi42OGgwWiIvPjxnPjxnPjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik03Myw1Ni45SDMxYy0yLjA3LDAtMy43NS0xLjY4LTMuNzUtMy43NXMxLjY4LTMuNzUsMy43NS0zLjc1aDQyYzIuMDcsMCwzLjc1LDEuNjgsMy43NSwzLjc1cy0xLjY4LDMuNzUtMy43NSwzLjc1Wk0zMSw1MC45Yy0xLjI0LDAtMi4yNSwxLjAxLTIuMjUsMi4yNXMxLjAxLDIuMjUsMi4yNSwyLjI1aDQyYzEuMjQsMCwyLjI1LTEuMDEsMi4yNS0yLjI1cy0xLjAxLTIuMjUtMi4yNS0yLjI1SDMxWiIvPjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0zMiw1NS4yNWMtLjMzLDAtLjYtLjI3LS42LS42di0zYzAtLjMzLjI3LS42LjYtLjZzLjYuMjcuNi42djNjMCwuMzMtLjI3LjYtLjYuNloiLz48L2c+PHBhdGggY2xhc3M9InN0MSIgZD0iTTM1LDU4LjUzaDM0YzEuNjYsMCwzLDEuMzQsMywzaDBjMCwxLjY2LTEuMzQsMy0zLDNoLTM0Yy0xLjY2LDAtMy0xLjM0LTMtM2gwYzAtMS42NiwxLjM0LTMsMy0zWiIvPjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0zOSw2Ni4xNWgyNmMxLjY2LDAsMywxLjM0LDMsM2gwYzAsMS42Ni0xLjM0LDMtMywzaC0yNmMtMS42NiwwLTMtMS4zNC0zLTNoMGMwLTEuNjYsMS4zNC0zLDMtM1oiLz48L2c+PC9zdmc+',
        'fieldforge' => 'IDxzdmcgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMTA0IDEwNiI+PCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI5LjguMywgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDIuMS4xIEJ1aWxkIDMpIC0tPjxkZWZzPjxzdHlsZT4gLnN0MCB7IG9wYWNpdHk6IC41OyB9IC5zdDAsIC5zdDEsIC5zdDIsIC5zdDMsIC5zdDQsIC5zdDUgeyBmaWxsOiAjZjQzZjVlOyB9IC5zdDAsIC5zdDEsIC5zdDIsIC5zdDMsIC5zdDUgeyBpc29sYXRpb246IGlzb2xhdGU7IH0gLnN0NiB7IGZpbGw6ICNkZGQ5Yzg7IH0gLnN0MSB7IG9wYWNpdHk6IC42OyB9IC5zdDIgeyBvcGFjaXR5OiAuODsgfSAuc3QzIHsgb3BhY2l0eTogLjM7IH0gLnN0NSB7IG9wYWNpdHk6IC40OyB9IDwvc3R5bGU+PC9kZWZzPjxwYXRoIGNsYXNzPSJzdDYiIGQ9Ik0xMDEuMiwxMDUuNDljLS43LjM0LTEuNDUuNTEtMi4xOS41MS0xLjg1LDAtMy42My0xLjAzLTQuNS0yLjhMNTIsMTYuMzcsOS40OSwxMDMuMmMtLjg2LDEuNzctMi42NCwyLjgtNC40OSwyLjgtLjc0LDAtMS40OS0uMTctMi4xOS0uNTEtMi40OC0xLjIyLTMuNTEtNC4yMS0yLjMtNi42OUw0Ny41MSwyLjhzLjA0LS4wNy4wNi0uMTFjLjA1LS4xLjExLS4yLjE3LS4zcy4xMy0uMi4yLS4zYy4wNi0uMDkuMTMtLjE4LjItLjI2LjQ0LS41NCwxLTEsMS42Ny0xLjMyLjAxLDAsLjAyLS4wMS4wMy0uMDEuMTYtLjA4LjMyLS4xNS40OC0uMjEuMTMtLjA0LjI2LS4wOC4zOS0uMTEuMDktLjAzLjE4LS4wNS4yNy0uMDcuMTgtLjA0LjM2LS4wNy41NC0uMDguMTctLjAyLjMzLS4wMy40OS0uMDNzLjMyLjAxLjQ4LjAzYy4xOC4wMS4zNi4wNC41NC4wOC4wOS4wMi4xOC4wNC4yNy4wNi4xMy4wMy4yNi4wNy4zOS4xMi4xNi4wNi4zMi4xMy40OC4yMS4xNi4wNy4zMi4xNS40Ny4yNS4wOS4wNi4xOC4xMS4yNi4xOC40NS4zMS44NS43MSwxLjE3LDEuMTcuMDcuMDguMTIuMTcuMTguMjYuMDguMTQuMTcuMjkuMjQuNDRsLjA5LjE5LDQ2LjkxLDk1LjgxYzEuMjIsMi40OC4xOSw1LjQ3LTIuMjksNi42OVoiLz48Zz48cmVjdCBjbGFzcz0ic3QwIiB4PSIzNiIgeT0iNTEiIHdpZHRoPSI4IiBoZWlnaHQ9IjUiIHJ4PSIxLjUiIHJ5PSIxLjUiLz48cmVjdCBjbGFzcz0ic3QzIiB4PSI0NiIgeT0iNTEiIHdpZHRoPSIxOCIgaGVpZ2h0PSI1IiByeD0iMS41IiByeT0iMS41Ii8+PHJlY3QgY2xhc3M9InN0MSIgeD0iMzgiIHk9IjU5IiB3aWR0aD0iOCIgaGVpZ2h0PSI1IiByeD0iMS41IiByeT0iMS41Ii8+PHJlY3QgY2xhc3M9InN0NSIgeD0iNDgiIHk9IjU5IiB3aWR0aD0iMTgiIGhlaWdodD0iNSIgcng9IjEuNSIgcnk9IjEuNSIvPjxyZWN0IGNsYXNzPSJzdDIiIHg9IjQwIiB5PSI2NyIgd2lkdGg9IjgiIGhlaWdodD0iNSIgcng9IjEuNSIgcnk9IjEuNSIvPjxyZWN0IGNsYXNzPSJzdDQiIHg9IjUwIiB5PSI2NyIgd2lkdGg9IjE4IiBoZWlnaHQ9IjUiIHJ4PSIxLjUiIHJ5PSIxLjUiLz48L2c+PC9zdmc+',
    ];


    public static function instance() {
        if (!self::$instance) self::$instance = new self();
        return self::$instance;
    }

    private function __construct() {
        // F13 phase 3: AJAX handlers live in Forge_Suite_Dashboard_Activation.
        // Load + register once; class_exists guards prevent double-loading
        // when multiple Forge plugins are active.
        if (!class_exists('Forge_Suite_Dashboard_Activation')) {
            require_once __DIR__ . '/class-forge-dashboard-activation.php';
        }
        if (class_exists('Forge_Suite_Dashboard_Activation')) {
            Forge_Suite_Dashboard_Activation::register();
        }
        // F13 phase 4: card renderers + stat collectors in Forge_Suite_Dashboard_Cards.
        // Pure static utility — no registration needed, just load so
        // calls in render_dashboard_page resolve.
        if (!class_exists('Forge_Suite_Dashboard_Cards')) {
            require_once __DIR__ . '/class-forge-dashboard-cards.php';
        }
        // F13 phase 5: bundle CTA banner + license probes in
        // Forge_Suite_Dashboard_Bundle_CTA. Same pure-static shape.
        if (!class_exists('Forge_Suite_Dashboard_Bundle_CTA')) {
            require_once __DIR__ . '/class-forge-dashboard-bundle-cta.php';
        }
        // F13 phase 6: Bundle License activation section (status banner,
        // Connect CTA, manual-key fallback, per-plugin pills) in
        // Forge_Suite_Dashboard_License_Activation. Same static shape.
        if (!class_exists('Forge_Suite_Dashboard_License_Activation')) {
            require_once __DIR__ . '/class-forge-dashboard-license-activation.php';
        }
        if (!class_exists('Forge_Suite_Dashboard_Health')) {
            require_once __DIR__ . '/class-forge-dashboard-health.php';
        }
        if (class_exists('Forge_Suite_Dashboard_Health')) {
            Forge_Suite_Dashboard_Health::register();
        }
        add_action('admin_init', [__CLASS__, 'check_freemius_sdk_pin']);
        add_action('admin_enqueue_scripts', [__CLASS__, 'enqueue_assets']);
    }

    /**
 * Enqueue dashboard CSS. Hooked on admin_enqueue_scripts and gated on
 * the forge-suite page suffix so we don't leak ~650 lines of styles
 * onto every admin screen. Previously this CSS was echoed inline from
 * render_styles — 2026-04-21 extracted to assets/css/forge-dashboard.css
 * as the first step of the class-forge-dashboard.php decomposition.
 */
    public static function enqueue_assets($hook) {
        if (strpos((string) $hook, 'forge-suite') === false) return;
        $base_url  = plugin_dir_url(__FILE__);               // .../plugins/{slug}/includes/
        $base_path = plugin_dir_path(__FILE__);
        $assets_url  = rtrim(dirname($base_url, 1), '/') . '/assets/';
        $assets_path = rtrim(dirname($base_path, 1), '/') . '/assets/';

        // CSS
        $css_file = $assets_path . 'css/forge-dashboard.css';
        $css_ver  = file_exists($css_file) ? (string) filemtime($css_file) : '2026-04-21';
        wp_enqueue_style('forge-suite-dashboard', $assets_url . 'css/forge-dashboard.css', [], $css_ver);

        // JS (F12: extracted from render_license_activation).
        // Localize BEFORE enqueue so ForgeDashboardCfg is available in the
        // same document emit pass as the script tag.
        $js_file = $assets_path . 'js/forge-dashboard.js';
        $js_ver  = file_exists($js_file) ? (string) filemtime($js_file) : '2026-04-21';
        wp_enqueue_script(
            'forge-suite-dashboard',
            $assets_url . 'js/forge-dashboard.js',
            ['jquery'],
            $js_ver,
            true
        );

        // FC-63 v2: manual bundle-key activation is removed, so the per-plugin
        // Freemius activate-license AJAX config and the license-scope nonce are
        // gone. Only the Disconnect + health-diagnostics flows remain.
        wp_localize_script('forge-suite-dashboard', 'ForgeDashboardCfg', [
            'apiUrl'      => (defined('FORGE_API_URL') && FORGE_API_URL) ? rtrim(FORGE_API_URL, '/') : 'https://api.avakode.com',
            'siteUrl'     => home_url('/'),
            'deactNonce'  => wp_create_nonce('forge_suite_deactivate_bundle'),
            'healthNonce' => wp_create_nonce('forge_suite_health_check'),
            'i18n'        => [
                'confirmDeact'       => __('Disconnect this site from Avakode? All Forge plugins return to Free until you reconnect.', 'rankforge'),
                'deactivating'       => __('Disconnecting...', 'rankforge'),
                'deactivationFailed' => __('Disconnect failed.', 'rankforge'),
                'networkError'       => __('Network error.', 'rankforge'),
                'runningHealth'      => __('Running diagnostics...', 'rankforge'),
                'healthFailed'       => __('Diagnostics failed.', 'rankforge'),
                'diagnosticHistory'  => __('Diagnostic history', 'rankforge'),
                'clearHistory'       => __('Clear history', 'rankforge'),
                'noDiagnosticRuns'   => __('No diagnostic runs yet.', 'rankforge'),
                'supportPackage'     => __('Support package', 'rankforge'),
                /* translators: %1$d: number of OK checks, %2$d: number of warnings, %3$d: number of errors. */
                'healthCounts'       => __('%1$d ok, %2$d warnings, %3$d errors', 'rankforge'),
            ],
        ]);
    }

    /**
 * Warn if the Freemius SDK has been upgraded beyond the tested pin.
 * ajax_deactivate_bundle uses Reflection on the private Freemius API
 * `_deactivate_license` and `get_api_site_scope` — breaks on SDK
 * upgrades. Tested on 2.13.x. If the SDK is upgraded (via composer or
 * by accepting an upstream SDK PR), this notice fires so we catch it
 * before the tester reports "Deactivate button does nothing".
 *
 * Pin rationale documented in forge-meta/FREEMIUS_SDK_PIN.md.
 */
    public static function check_freemius_sdk_pin() {
        if (!current_user_can('activate_plugins')) return;
        if (!defined('WP_FS__SDK_VERSION')) return;
        $version = WP_FS__SDK_VERSION;
        if (strpos($version, '2.13') === 0) return;

        add_action('admin_notices', function() use ($version) {
            if (!current_user_can('activate_plugins')) return;
            echo '<div class="notice notice-warning is-dismissible"><p>';
            printf(
                wp_kses( __( '<strong>Forge Suite:</strong> Freemius SDK is at <code>%1$s</code>, but the plugin is pinned to <code>2.13.x</code>. The license-deactivation flow uses Reflection on the private Freemius API and may break on newer SDK internals. Please report this at <a href="https://github.com/avakode/forge-meta/issues">avakode/forge-meta</a> before upgrading further.', 'rankforge' ), [ 'strong' => [], 'code' => [], 'a' => [ 'href' => [] ] ] ),
                esc_html( $version )
            );
            echo '</p></div>';
        });
    }

    /**
 * Count how many Forge plugins are active
 */
    public static function count_active_plugins() {
        $count = 0;
        if (class_exists('RANKFORGE_Core')) $count++;
        if (class_exists('LANGFORGE_Core')) $count++;
        if (class_exists('FORMFORGE_Core')) $count++;
        if (class_exists('FIELDFORGE_Core')) $count++;
        return $count;
    }

    /**
 * Always show when any Forge plugin is active
 */
    public static function should_show() {
        return self::count_active_plugins() >= 1;
    }

    /**
 * Register admin menu (call from each plugin — only first one takes effect)
 */
    public static function maybe_register_menu() {
        if (self::$menu_registered) return;
        if (!self::should_show()) return;
        self::$menu_registered = true;
        add_menu_page(
            'Forge Suite',
            'Forge Suite',
            'manage_options',
            'forge-suite',
            [self::instance(), 'render'],
            'dashicons-shield',
            26
        );
    }

    /**
 * Get status info for all 4 plugins
 */
    private function get_plugin_statuses() {
        return [
            'seo' => [
                'active'       => class_exists('RANKFORGE_Core'),
                'installed'    => file_exists(WP_PLUGIN_DIR . '/rankforge/rankforge.php'),
                'name'         => 'Rank Forge',
                'description'  => 'AI-powered SEO analysis, schema, rank tracking',
                'slug'         => 'rankforge',
                'icon'         => 'dashicons-chart-line',
                'color'        => '#FF6B35',
                'bg_light'     => '#FFF7ED',
                'border_light' => '#FED7AA',
                'install_url'  => admin_url('plugin-install.php?s=rank+forge+avakode&tab=search&type=term'),
                'activate_url' => wp_nonce_url(admin_url('plugins.php?action=activate&plugin=rankforge/rankforge.php'), 'activate-plugin_rankforge/rankforge.php'),
                'admin_url'    => admin_url('admin.php?page=rankforge'),
            ],
            'lang' => [
                'active'       => class_exists('LANGFORGE_Core'),
                'installed'    => file_exists(WP_PLUGIN_DIR . '/langforge/langforge.php'),
                'name'         => 'Lang Forge',
                'description'  => 'Multilingual translation with AI',
                'slug'         => 'langforge',
                'icon'         => 'dashicons-translation',
                'color'        => '#0EC49E',
                'bg_light'     => '#F0FDF9',
                'border_light' => '#99F6E0',
                'install_url'  => admin_url('plugin-install.php?s=lang+forge+avakode&tab=search&type=term'),
                'activate_url' => wp_nonce_url(admin_url('plugins.php?action=activate&plugin=langforge/langforge.php'), 'activate-plugin_langforge/langforge.php'),
                'admin_url'    => admin_url('admin.php?page=langforge-settings'),
            ],
            'form' => [
                'active'       => class_exists('FORMFORGE_Core'),
                'installed'    => file_exists(WP_PLUGIN_DIR . '/formforge/formforge.php'),
                'name'         => 'Form Forge',
                'description'  => 'Drag & drop form builder with AI',
                'slug'         => 'formforge',
                'icon'         => 'dashicons-feedback',
                'color'        => '#3B82F6',
                'bg_light'     => '#EFF6FF',
                'border_light' => '#BFDBFE',
                'install_url'  => admin_url('plugin-install.php?s=form+forge+avakode&tab=search&type=term'),
                'activate_url' => wp_nonce_url(admin_url('plugins.php?action=activate&plugin=formforge/formforge.php'), 'activate-plugin_formforge/formforge.php'),
                'admin_url'    => admin_url('admin.php?page=formforge'),
            ],
            'fieldforge' => [
                'active'       => class_exists('FIELDFORGE_Core'),
                'installed'    => file_exists(WP_PLUGIN_DIR . '/fieldforge/fieldforge.php'),
                'name'         => 'Field Forge',
                'description'  => 'Custom fields & Gutenberg blocks with schema versioning',
                'slug'         => 'fieldforge',
                'icon'         => 'dashicons-database',
                'color'        => '#F43F5E',
                'bg_light'     => '#FFF1F2',
                'border_light' => '#FECDD3',
                'install_url'  => admin_url('plugin-install.php?s=field+forge+avakode&tab=search&type=term'),
                'activate_url' => wp_nonce_url(admin_url('plugins.php?action=activate&plugin=fieldforge/fieldforge.php'), 'activate-plugin_fieldforge/fieldforge.php'),
                'admin_url'    => admin_url('admin.php?page=fieldforge'),
            ],
        ];
    }

    // ── Stats collectors ───────────────────────────────────────

    // F13 phase 4 (2026-04-21): get_seo_stats / get_lang_stats /
    // get_form_stats / get_fieldforge_stats moved to
    // class-forge-dashboard-cards.php as `Forge_Suite_Dashboard_Cards::get_*_stats`.
    // Call sites in render updated accordingly.

    // ── Recent Activity ────────────────────────────────────────

    private function get_recent_activity() {
        global $wpdb;
        $events = [];

        // SEO: recent analyses
        if (class_exists('RANKFORGE_Dashboard')) {
            $table = $wpdb->prefix . 'rankforge_analysis';
            if ($wpdb->get_var("SHOW TABLES LIKE '$table'") === $table) {
                $rows = $wpdb->get_results(
                    "SELECT a.post_id, a.score, a.analyzed_at, p.post_title
                     FROM $table a
                     INNER JOIN {$wpdb->posts} p ON a.post_id = p.ID
                     ORDER BY a.analyzed_at DESC LIMIT 5"
                );
                foreach ($rows as $r) {
                    $events[] = [
                        'time'   => strtotime($r->analyzed_at),
                        'plugin' => 'seo',
                        'icon'   => 'dashicons-chart-line',
                        'color'  => '#FF6B35',
                        'text'   => sprintf('SEO score %d for "%s"', $r->score, $r->post_title),
                    ];
                }
            }
        }

        // Lang: recent translations
        if (class_exists('LANGFORGE_Core')) {
            $table = $wpdb->prefix . 'langforge_analytics';
            if ($wpdb->get_var("SHOW TABLES LIKE '$table'") === $table) {
                $rows = $wpdb->get_results(
                    "SELECT a.post_id, a.language_code, a.created_at, p.post_title
                     FROM $table a
                     LEFT JOIN {$wpdb->posts} p ON a.post_id = p.ID
                     WHERE a.event_type = 'translation_created'
                     ORDER BY a.created_at DESC LIMIT 5"
                );
                foreach ($rows as $r) {
                    $title = $r->post_title ?: ('#' . $r->post_id);
                    $events[] = [
                        'time'   => strtotime($r->created_at),
                        'plugin' => 'lang',
                        'icon'   => 'dashicons-translation',
                        'color'  => '#0EC49E',
                        'text'   => sprintf('Translation [%s] created for "%s"', strtoupper($r->language_code), $title),
                    ];
                }
            }
        }

        // Form: recent submissions
        if (class_exists('FORMFORGE_Core')) {
            $table = $wpdb->prefix . 'formforge_submissions';
            $forms_table = $wpdb->prefix . 'formforge_forms';
            if ($wpdb->get_var("SHOW TABLES LIKE '$table'") === $table) {
                $rows = $wpdb->get_results(
                    "SELECT s.form_id, s.created_at, f.title AS form_title
                     FROM $table s
                     LEFT JOIN $forms_table f ON s.form_id = f.id
                     WHERE s.status != 'abandoned'
                     ORDER BY s.created_at DESC LIMIT 5"
                );
                foreach ($rows as $r) {
                    $fname = $r->form_title ?: ('Form #' . $r->form_id);
                    $events[] = [
                        'time'   => strtotime($r->created_at),
                        'plugin' => 'form',
                        'icon'   => 'dashicons-feedback',
                        'color'  => '#3B82F6',
                        'text'   => sprintf('New submission in "%s"', $fname),
                    ];
                }
            }
        }

        // Field Forge: recent field group changes
        if (class_exists('FIELDFORGE_Core')) {
            $table = $wpdb->prefix . 'fieldforge_revisions';
            if ($wpdb->get_var("SHOW TABLES LIKE '$table'") === $table) {
                $rows = $wpdb->get_results(
                    "SELECT r.group_id, r.version, r.title, r.created_at, u.display_name
                     FROM $table r
                     LEFT JOIN {$wpdb->users} u ON r.user_id = u.ID
                     ORDER BY r.created_at DESC LIMIT 5"
                );
                foreach ($rows as $r) {
                    $events[] = [
                        'time'   => strtotime($r->created_at),
                        'plugin' => 'fieldforge',
                        'icon'   => 'dashicons-database',
                        'color'  => '#F43F5E',
                        'text'   => sprintf('Field group "%s" updated (v%d)', $r->title, $r->version),
                    ];
                }
            }

            $val_table = $wpdb->prefix . 'fieldforge_values';
            if ($wpdb->get_var("SHOW TABLES LIKE '$val_table'") === $val_table) {
                $rows = $wpdb->get_results(
                    "SELECT v.post_id, p.post_title, COUNT(*) as field_count, MAX(v.id) as last_id
                     FROM $val_table v
                     INNER JOIN {$wpdb->posts} p ON v.post_id = p.ID
                     WHERE v.parent_id = 0
                     GROUP BY v.post_id
                     ORDER BY last_id DESC LIMIT 3"
                );
                foreach ($rows as $r) {
                    $events[] = [
                        'time'   => current_time('timestamp') - 3600,
                        'plugin' => 'fieldforge',
                        'icon'   => 'dashicons-database',
                        'color'  => '#F43F5E',
                        'text'   => sprintf('%d fields saved for "%s"', $r->field_count, $r->post_title),
                    ];
                }
            }
        }

        usort($events, function ($a, $b) { return $b['time'] - $a['time']; });
        return array_slice($events, 0, 10);
    }

    // ── Render ─────────────────────────────────────────────────

    public function render() {
        // F13 phase 4: stats collectors moved to Forge_Suite_Dashboard_Cards.
        $seo  = Forge_Suite_Dashboard_Cards::get_seo_stats();
        $lang = Forge_Suite_Dashboard_Cards::get_lang_stats();
        $form = Forge_Suite_Dashboard_Cards::get_form_stats();
        $fdf  = Forge_Suite_Dashboard_Cards::get_fieldforge_stats();
        $activity = $this->get_recent_activity();

        $active_count = self::count_active_plugins();
        $statuses = $this->get_plugin_statuses();

        $stats_map = [
            'seo'        => $seo,
            'lang'       => $lang,
            'form'       => $form,
            'fieldforge' => $fdf,
        ];
        ?>
        <div class="wrap forge-suite-wrap">
            <h1 class="wp-heading-inline" style="display:none;">Forge Suite</h1>

            <!-- Header -->
            <div class="forge-suite-header">
                <div class="forge-suite-header-inner">
                    <span class="dashicons dashicons-shield" style="font-size:28px;width:28px;height:28px;color:#fff;opacity:0.9;"></span>
                    <div>
                        <h1>Forge Suite</h1>
                        <p><?php /* translators: %d: number of Forge Suite plugins currently active. */ echo esc_html(sprintf(__('%d of 4 plugins active', 'rankforge'), (int) $active_count)); ?></p>
                    </div>
                    <?php
                    // Show the credits chip only when the user has a bundle
                    // license (pooled credits across all 4 plugins). On
                    // individual-plugin sites the chip lives on each plugin's
                    // own admin header, not on the Forge Suite overview.
                    if (class_exists('Forge_Credits_Widget') && Forge_Suite_Dashboard_Bundle_CTA::user_has_bundle()) {
                        Forge_Credits_Widget::render_chip_in_header();
                    }
                    ?>
                </div>
            </div>

            <?php
            // v5 (2026-05-05): tester reported that after Connect from
            // Forge Suite dashboard, /auth/exchange returns SITE_MISMATCH and
            // redirects back here with `?forge_connect_error=...&forge_connect_code=SITE_MISMATCH&forge_connect_move_url=...`,
            // but the dashboard NEVER rendered the actionable banner —
            // those query args were only consumed on the per-plugin License
            // page (`langforge-license`). Render the banner here too, with
            // an explicit "Move license to this site" button when SITE_MISMATCH
            // is present.
            self::render_connect_result_banner();
            ?>

            <!-- Bundle CTA Banner (F13 phase 5: extracted) -->
            <?php Forge_Suite_Dashboard_Bundle_CTA::render_banner($statuses); ?>

            <!-- Plugin Cards — always all 4 -->
            <div class="forge-suite-cards">
                <?php
                foreach ($statuses as $key => $plugin) {
                    $stats = $stats_map[$key];
                    // F13 phase 4: card renderers moved to Forge_Suite_Dashboard_Cards.
                    if ($plugin['active'] && $stats) {
                        Forge_Suite_Dashboard_Cards::render_active_card($key, $plugin, $stats);
                    } elseif ($plugin['installed']) {
                        Forge_Suite_Dashboard_Cards::render_inactive_card($plugin);
                    } else {
                        Forge_Suite_Dashboard_Cards::render_not_installed_card($plugin);
                    }
                }
                ?>
            </div>

            <!-- Bundle License Activation (F13 phase 6: extracted) -->
            <?php Forge_Suite_Dashboard_License_Activation::render(); ?>

            <?php if (class_exists('Forge_Suite_Dashboard_Health')) { Forge_Suite_Dashboard_Health::render(); } ?>

            <?php self::render_guided_setup($seo, $lang, $form, $fdf); ?>

            <!-- Recent Activity -->
            <?php if (!empty($activity)): ?>
            <div class="forge-suite-section">
                <h2 class="forge-suite-section-title">
                    <span class="dashicons dashicons-list-view" style="color:#7C5CFC;"></span>
                    <?php esc_html_e('Recent Activity', 'rankforge'); ?>
                </h2>
                <div class="forge-suite-activity">
                    <?php foreach ($activity as $event): ?>
                    <div class="forge-suite-activity-item">
                        <span class="dashicons <?php echo esc_attr($event['icon']); ?>" style="color:<?php echo esc_attr($event['color']); ?>;"></span>
                        <div class="forge-suite-activity-text"><?php echo esc_html($event['text']); ?></div>
                        <div class="forge-suite-activity-time"><?php echo esc_html(human_time_diff($event['time'], current_time('timestamp'))); ?> <?php esc_html_e('ago', 'rankforge'); ?></div>
                    </div>
                    <?php endforeach; ?>
                </div>
            </div>
            <?php endif; ?>

            <!-- Quick Actions (only for active plugins) -->
            <div class="forge-suite-section">
                <h2 class="forge-suite-section-title">
                    <span class="dashicons dashicons-admin-tools" style="color:#7C5CFC;"></span>
                    <?php esc_html_e('Quick Actions', 'rankforge'); ?>
                </h2>
                <div class="forge-suite-actions">
                    <?php if ($seo): ?>
                    <a href="<?php echo esc_url(admin_url('admin.php?page=rankforge-audit')); ?>" class="forge-suite-action forge-suite-action--seo">
                        <span class="dashicons dashicons-search"></span>
                        <span class="forge-suite-action-label"><?php esc_html_e('Site Audit', 'rankforge'); ?></span>
                        <span class="forge-suite-action-desc"><?php esc_html_e('Full SEO analysis', 'rankforge'); ?></span>
                    </a>
                    <a href="<?php echo esc_url(admin_url('admin.php?page=rankforge-settings')); ?>" class="forge-suite-action forge-suite-action--seo">
                        <span class="dashicons dashicons-database"></span>
                        <span class="forge-suite-action-label"><?php esc_html_e('Bulk Analyze', 'rankforge'); ?></span>
                        <span class="forge-suite-action-desc"><?php esc_html_e('Analyze all posts', 'rankforge'); ?></span>
                    </a>
                    <?php endif; ?>

                    <?php if ($lang): ?>
                    <a href="<?php echo esc_url(admin_url('admin.php?page=langforge-strings')); ?>" class="forge-suite-action forge-suite-action--lang">
                        <span class="dashicons dashicons-translation"></span>
                        <span class="forge-suite-action-label"><?php esc_html_e('Bulk Translate', 'rankforge'); ?></span>
                        <span class="forge-suite-action-desc"><?php esc_html_e('Translate strings in bulk', 'rankforge'); ?></span>
                    </a>
                    <a href="<?php echo esc_url(admin_url('admin.php?page=langforge-glossary')); ?>" class="forge-suite-action forge-suite-action--lang">
                        <span class="dashicons dashicons-book"></span>
                        <span class="forge-suite-action-label"><?php esc_html_e('Glossary', 'rankforge'); ?></span>
                        <span class="forge-suite-action-desc"><?php esc_html_e('Manage terms', 'rankforge'); ?></span>
                    </a>
                    <?php endif; ?>

                    <?php if ($form): ?>
                    <a href="<?php echo esc_url(admin_url('admin.php?page=formforge-new')); ?>" class="forge-suite-action forge-suite-action--form">
                        <span class="dashicons dashicons-plus-alt2"></span>
                        <span class="forge-suite-action-label"><?php esc_html_e('New Form', 'rankforge'); ?></span>
                        <span class="forge-suite-action-desc"><?php esc_html_e('Create a new form', 'rankforge'); ?></span>
                    </a>
                    <a href="<?php echo esc_url(admin_url('admin.php?page=formforge')); ?>" class="forge-suite-action forge-suite-action--form">
                        <span class="dashicons dashicons-feedback"></span>
                        <span class="forge-suite-action-label"><?php esc_html_e('All Forms', 'rankforge'); ?></span>
                        <span class="forge-suite-action-desc"><?php esc_html_e('Manage forms', 'rankforge'); ?></span>
                    </a>
                    <?php endif; ?>

                    <?php if ($fdf): ?>
                    <a href="<?php echo esc_url(admin_url('admin.php?page=fieldforge-new')); ?>" class="forge-suite-action forge-suite-action--fieldforge">
                        <span class="dashicons dashicons-plus-alt2"></span>
                        <span class="forge-suite-action-label"><?php esc_html_e('New Field Group', 'rankforge'); ?></span>
                        <span class="forge-suite-action-desc"><?php esc_html_e('Create fields', 'rankforge'); ?></span>
                    </a>
                    <a href="<?php echo esc_url(admin_url('admin.php?page=fieldforge')); ?>" class="forge-suite-action forge-suite-action--fieldforge">
                        <span class="dashicons dashicons-database"></span>
                        <span class="forge-suite-action-label"><?php esc_html_e('Field Groups', 'rankforge'); ?></span>
                        <span class="forge-suite-action-desc"><?php esc_html_e('Manage fields', 'rankforge'); ?></span>
                    </a>
                    <?php endif; ?>
                </div>
            </div>

        </div>
        <?php $this->render_styles(); ?>
        <?php
    }

    private static function render_guided_setup($seo, $lang, $form, $fdf) {
        $guides = [
            [
                'class' => 'seo',
                'active' => (bool) $seo,
                'title' => __('Rank Forge setup', 'rankforge'),
                'desc' => __('Start with crawl basics, then connect Google data and run a full audit.', 'rankforge'),
                'steps' => [
                    [__('Review SEO settings', 'rankforge'), admin_url('admin.php?page=rankforge-settings')],
                    [__('Connect Google Search Console', 'rankforge'), admin_url('admin.php?page=rankforge-settings#sf-tab-integrations')],
                    [__('Run Site Audit', 'rankforge'), admin_url('admin.php?page=rankforge-audit')],
                ],
            ],
            [
                'class' => 'lang',
                'active' => (bool) $lang,
                'title' => __('Lang Forge setup', 'rankforge'),
                'desc' => __('Configure languages, lock terminology, then translate priority content.', 'rankforge'),
                'steps' => [
                    [__('Choose site languages', 'rankforge'), admin_url('admin.php?page=langforge-settings')],
                    [__('Build glossary', 'rankforge'), admin_url('admin.php?page=langforge-glossary')],
                    [__('Translate strings', 'rankforge'), admin_url('admin.php?page=langforge-strings')],
                ],
            ],
            [
                'class' => 'form',
                'active' => (bool) $form,
                'title' => __('Form Forge setup', 'rankforge'),
                'desc' => __('Create a form, configure delivery, then test every connected integration.', 'rankforge'),
                'steps' => [
                    [__('Create or import a form', 'rankforge'), admin_url('admin.php?page=formforge-new')],
                    [__('Configure notifications', 'rankforge'), admin_url('admin.php?page=formforge-settings')],
                    [__('Connect CRM and Google integrations', 'rankforge'), admin_url('admin.php?page=formforge-settings#integrations')],
                ],
            ],
            [
                'class' => 'fieldforge',
                'active' => (bool) $fdf,
                'title' => __('Field Forge setup', 'rankforge'),
                'desc' => __('Create field groups or import ACF fields, then verify frontend rendering.', 'rankforge'),
                'steps' => [
                    [__('Create a field group', 'rankforge'), admin_url('admin.php?page=fieldforge-new')],
                    [__('Import from ACF', 'rankforge'), admin_url('admin.php?page=fieldforge-import')],
                    [__('Review field groups', 'rankforge'), admin_url('admin.php?page=fieldforge')],
                ],
            ],
        ];
        ?>
        <div class="forge-suite-section forge-suite-guided-setup">
            <h2 class="forge-suite-section-title">
                <span class="dashicons dashicons-controls-forward" style="color:#7C5CFC;"></span>
                <?php esc_html_e('Guided setup', 'rankforge'); ?>
            </h2>
            <div class="forge-suite-guides">
                <?php foreach ($guides as $guide): ?>
                    <div class="forge-suite-guide forge-suite-guide--<?php echo esc_attr($guide['class']); ?> <?php echo $guide['active'] ? 'is-active' : 'is-inactive'; ?>">
                        <div class="forge-suite-guide-head">
                            <strong><?php echo esc_html($guide['title']); ?></strong>
                            <span><?php echo $guide['active'] ? esc_html__('Active', 'rankforge') : esc_html__('Inactive', 'rankforge'); ?></span>
                        </div>
                        <p><?php echo esc_html($guide['desc']); ?></p>
                        <ol>
                            <?php foreach ($guide['steps'] as $step): ?>
                                <li><a href="<?php echo esc_url($step[1]); ?>"><?php echo esc_html($step[0]); ?></a></li>
                            <?php endforeach; ?>
                        </ol>
                    </div>
                <?php endforeach; ?>
            </div>
        </div>
        <?php
    }

    /**
 * v5 (2026-05-05): render Connect-flow result banner from
 * the query params left by Forge_Connect::maybe_handle_return.
 *
 * Three states:
 * - forge_connect_success=1 → green "Connected" banner
 * - forge_connect_error + code=SITE_MISMATCH → yellow "Move license" banner with
 * the actionable button + hostnames
 * - forge_connect_error (other code) → red "Connect failed" banner
 *
 * The banner used to live ONLY on the per-plugin License page
 * (`class-license-admin.php` — class scoped to `lf-license`),
 * but the user clicks "Connect to Avakode" from the Forge Suite
 * dashboard hub. Forge_Connect's redirect_uri preserves whichever
 * page initiated the flow, so on SITE_MISMATCH the user lands
 * back on `?page=forge-suite&forge_connect_error=…` — the dashboard
 * needs to surface that here, otherwise the user just sees a
 * silent dashboard with all plugins still labelled FREE / NOT CONNECTED.
 */
    public static function render_connect_result_banner() {
        if (!empty($_GET['forge_connect_moved'])) {
            echo '<div class="notice notice-success" style="margin:16px 0;border-radius:10px;border-left-width:4px;padding:14px 18px;"><p style="margin:0;font-size:14px;"><strong>'
                . esc_html__('License moved successfully.', 'rankforge')
                . '</strong> ' . esc_html__('Click Connect to finish activation on this site.', 'rankforge') . '</p></div>';
            return;
        }
        if (!empty($_GET['forge_connect_success'])) {
            echo '<div class="notice notice-success" style="margin:16px 0;border-radius:10px;border-left-width:4px;padding:14px 18px;"><p style="margin:0;font-size:14px;"><strong>'
                . esc_html__('Connected to Avakode successfully!', 'rankforge')
                . '</strong> ' . esc_html__('Your license is active.', 'rankforge') . '</p></div>';
            return;
        }
        if (empty($_GET['forge_connect_error'])) return;

        $err      = sanitize_text_field(wp_unslash((string) $_GET['forge_connect_error']));
        $code     = !empty($_GET['forge_connect_code']) ? sanitize_text_field(wp_unslash((string) $_GET['forge_connect_code'])) : '';
        $move_url = !empty($_GET['forge_connect_move_url']) ? esc_url_raw(wp_unslash((string) $_GET['forge_connect_move_url'])) : '';

        // SITE_MISMATCH: the worker tells us this license is already
        // active on another site, with a `move_url` deep-linking the
        // user into the Avakode dashboard's "Move license" confirm flow.
        // We also infer SITE_MISMATCH from message shape so even if the
        // worker omitted forge_connect_code (older response), the user
        // still sees the banner.
        $is_site_mismatch = ($code === 'SITE_MISMATCH')
            || (stripos($err, 'currently active on') !== false)
            || (stripos($err, 'site_mismatch') !== false)
            || (stripos($err, 'site mismatch') !== false);

        if ($is_site_mismatch) {
            // Yellow / actionable banner.
            ?>
            <div class="notice notice-warning" style="margin:16px 0;border-radius:10px;border-left-width:4px;padding:14px 18px;background:#fffbea;border-left-color:#dba617;">
                <p style="margin:0 0 8px;font-size:14px;">
                    <strong><?php esc_html_e('This license is already active on another site.', 'rankforge'); ?></strong>
                </p>
                <p style="margin:0 0 12px;font-size:13px;color:#475569;">
                    <?php echo esc_html($err); ?>
                </p>
                <?php if ($move_url) : ?>
                    <p style="margin:0;">
                        <a href="<?php echo esc_url($move_url); ?>" class="button button-primary" target="_blank" rel="noopener" style="background:#dba617;border-color:#dba617;">
                            <?php esc_html_e('Move license to this site →', 'rankforge'); ?>
                        </a>
                        <em style="margin-left:10px;color:#64748b;font-size:13px;"><?php esc_html_e('Opens your Avakode dashboard in a new tab.', 'rankforge'); ?></em>
                    </p>
                <?php else : ?>
                    <p style="margin:0;font-size:13px;color:#64748b;">
                        <?php
                        printf(
                            /* translators: %s: avakode dashboard URL */
                            esc_html__('Open your %s to move the license to this site, then click Connect again.', 'rankforge'),
                            '<a href="' . esc_url(self::avakode_dashboard_url()) . '" target="_blank" rel="noopener">' . esc_html__('Avakode dashboard', 'rankforge') . '</a>'
                        );
                        ?>
                    </p>
                <?php endif; ?>
            </div>
            <?php
            return;
        }

        // Generic Connect failure — red banner.
        echo '<div class="notice notice-error" style="margin:16px 0;border-radius:10px;border-left-width:4px;padding:14px 18px;"><p style="margin:0;font-size:14px;">'
            . sprintf(
                /* translators: %s: error message */
                esc_html__('Connect failed: %s', 'rankforge'),
                esc_html($err)
            )
            . '</p></div>';
    }

    /** Helper: fall-back avakode dashboard URL when no move_url is provided. */
    private static function avakode_dashboard_url() {
        if (defined('FORGE_AVAKODE_URL') && FORGE_AVAKODE_URL) {
            return rtrim(FORGE_AVAKODE_URL, '/') . '/dashboard/licenses/';
        }
        return 'https://avakode.com/dashboard/licenses/';
    }

    // ── Card renderers ─────────────────────────────────────────

    /** Inline SVG logo for a plugin slug. $class optional for styling. */
    // F13 phase 4 (2026-04-21): made public so Forge_Suite_Dashboard_Cards
    // can call it cross-class via Forge_Suite_Dashboard::logo_img(...).
    public static function logo_img($slug, $class = 'forge-suite-logo') {
        if (empty(self::LOGOS_B64[$slug])) return '';
        return '<img src="data:image/svg+xml;base64,' . esc_attr(self::LOGOS_B64[$slug])
            . '" alt="" class="' . esc_attr($class) . '" aria-hidden="true">';
    }

    // ── Card renderers ─────────────────────────────────────────

    // F13 phase 4 (2026-04-21): render_active_card / render_inactive_card /
    // render_not_installed_card / render_{seo,lang,form,fieldforge}_card_body /
    // render_lang_progress_bar / render_flag_img all moved to
    // class-forge-dashboard-cards.php. logo_img above stays here so the
    // cards class can call it via `Forge_Suite_Dashboard::logo_img(...)`.
    // Call sites in render updated accordingly.

    // ── Bundle CTA ─────────────────────────────────────────────

    // F13 phase 5 (2026-04-21): render_bundle_banner + user_has_bundle +
    // all_plugins_pro moved to class-forge-dashboard-bundle-cta.php as
    // static methods on Forge_Suite_Dashboard_Bundle_CTA.
    // Call sites in render and the credits-chip header updated accordingly.

    // ── License Activation ─────────────────────────────────────
    // F13 phase 6 (2026-04-21): entire render_license_activation body
    // (~155 lines) moved to Forge_Suite_Dashboard_License_Activation::render.
    // Facade calls it directly; no delegator kept here because the old
    // method was `private` and had no external callers.

    /**
 * AJAX: Activate bundle license key across all plugins
 */
    /**
 * DEPRECATED (F13 phase 3): AJAX activation moved to
 * Forge_Suite_Dashboard_Activation::handle_activate. This method is
 * kept as a thin delegator for one release so external callers who
 * hooked `wp_ajax_forge_suite_activate_bundle` via the old
 * [$instance, 'ajax_activate_bundle'] target don't silently break.
 * Remove after two clean tester weeks.
 */
    public function ajax_activate_bundle() {
        if (!class_exists('Forge_Suite_Dashboard_Activation')) {
            require_once __DIR__ . '/class-forge-dashboard-activation.php';
        }
        Forge_Suite_Dashboard_Activation::handle_activate();
    }

    /**
 * DEPRECATED (F13 phase 3): AJAX deactivation moved to
 * Forge_Suite_Dashboard_Activation::handle_deactivate. Same
 * deprecation window as ajax_activate_bundle above.
 */
    public function ajax_deactivate_bundle() {
        if (!class_exists('Forge_Suite_Dashboard_Activation')) {
            require_once __DIR__ . '/class-forge-dashboard-activation.php';
        }
        Forge_Suite_Dashboard_Activation::handle_deactivate();
    }

    // ── Styles ─────────────────────────────────────────────────

    private function render_styles() {
        // CSS now lives in assets/css/forge-dashboard.css, enqueued via
        // enqueue_assets on admin_enqueue_scripts (see constructor).
        // 2026-04-21: extracted 647 lines of inline <style> from this
        // class as phase 1 of the god-class decomposition.
    }
}

} // end: if (!class_exists('Forge_Suite_Dashboard'))
