using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using TMPro;
using GeoSus.Client;

/// <summary>
/// Lives on host lobby.unity - the *settings* screen reached when the host
/// chooses "Host" from the main menu. Originally three fixed-position
/// controls (radius slider + impostor/task steppers). P13c expanded this to
/// a full scrollable settings panel covering every per-lobby setting the
/// server accepts: round shape, distances (kill / report / task / meeting /
/// emergency / repair), cooldowns (kill / emergency / sabotage), meeting
/// phase timings (arrival / late / discussion / voting), and sabotage
/// timings (comms / meltdown / repair hold). All values get accumulated
/// into GameManager.pendingSettings (a GameSettingsOverrides) and shipped
/// with the CreateLobby request so the server can stamp them into the
/// per-lobby snapshot at lobby creation time.
///
/// The scene already contains the art team's named UI:
///   Canvas
///   ├── Panel              - full-screen background
///   ├── RawImage           - decorative logo
///   ├── radius             - TMP text "Game radius:\n"  (HIDDEN, P13c)
///   ├── idk                - TMP text container         (HIDDEN, P13c)
///   ├── back               - back button
///   ├── Zmekole_geosusv2   - 3D globe object
///   └── stvořit            - "Create Lobby" button
///
/// We DO NOT destroy any of these. Instead we resolve them by name, wire
/// the buttons, hide the now-unused labels, and inject a ScrollRect with
/// the full settings panel anchored above the "stvořit" button. Anchor
/// offsets are in the canvas's portrait reference units (1080x1920).
/// </summary>
public class HostLobbyUI : MonoBehaviour
{
    // ── Colour palette (forwarded from UITheme) ──────────────────────────────
    static readonly Color C_TRACK   = UITheme.SurfaceDim;
    static readonly Color C_FILL    = UITheme.Accent;
    static readonly Color C_HANDLE  = UITheme.TextHi;
    static readonly Color C_BTN_BG  = UITheme.Surface;
    static readonly Color C_BTN_HI  = UITheme.Accent;
    static readonly Color C_TEXT    = UITheme.TextHi;
    static readonly Color C_HEADER  = UITheme.Accent;

    // ── Live values (mirrored into GameManager.pending* + pendingSettings) ──
    private float  _radius                 = 500f;
    private int    _impostors              = 1;
    private int    _tasks                  = 5;
    private int    _maxPlayers             = 10;
    private int    _killDist               = 5;
    private int    _reportDist             = 5;
    private int    _taskDist               = 5;
    private int    _meetingArrival         = 10;
    private int    _emergencyCallRadius    = 5;
    private int    _repairDist             = 5;
    private int    _killCooldownS          = 20;
    private int    _emergencyCooldownS     = 60;
    private int    _maxEmergencyMeetings   = 1;
    private int    _arrivalBaseS           = 60;
    private int    _allowedLateS           = 10;
    private int    _discussionS            = 60;
    private int    _votingS                = 60;
    private int    _sabotageCooldownS      = 60;
    private int    _commsDurationS         = 60;
    private int    _meltdownDeadlineS      = 90;
    private int    _repairHoldS            = 10;

    // ── Layout constants ────────────────────────────────────────────────────
    const float ROW_HEIGHT     = 130f;
    const float HEADER_HEIGHT  = 90f;
    const float SLIDER_HEIGHT  = 160f;

    void Start()
    {
        // Wire emoji fallback before any TMP component renders, so labels
        // with emoji glyphs display properly on the very first frame rather
        // than as tofu boxes that get fixed on a later refresh.
        UITheme.EnsureEmojiFontFallback();

        var gm = GameManager.Instance;
        if (gm != null)
        {
            // Carry over whatever the host had previously selected (if they
            // back out and come back in, settings survive).
            _radius    = (float)gm.pendingRadius;
            _impostors = gm.pendingImpostorCount;
            _tasks     = gm.pendingTaskCount;

            // Kick off GPS init NOW so by the time the host taps "Create
            // Lobby" we have a real position fix to seed the play area with.
            gm.inputSubsystem?.EnsureGPSStarted();
        }

        var canvasGO = GameObject.Find("Canvas");
        if (canvasGO == null)
        {
            Debug.LogError("[HostLobbyUI] No Canvas found in host lobby scene.");
            return;
        }
        var canvas = canvasGO.transform;

        // ── Bind existing scene buttons ─────────────────────────────────────
        var backBtn = FindButton(canvas, "back");
        if (backBtn != null)
        {
            backBtn.onClick.RemoveAllListeners();
            backBtn.onClick.AddListener(() => SceneManager.LoadScene("main menu asi idk lol"));
        }
        else Debug.LogWarning("[HostLobbyUI] 'back' button not found.");

        var createBtn = FindButton(canvas, "stvořit");
        if (createBtn != null)
        {
            createBtn.onClick.RemoveAllListeners();
            createBtn.onClick.AddListener(OnCreateClicked);
        }
        else Debug.LogWarning("[HostLobbyUI] 'stvořit' button not found.");

        // ── Hide the art team's "radius" / "idk" labels ─────────────────────
        // They were placeholders for the small set of settings we previously
        // exposed; the scrollable panel below now owns all of that real estate.
        var radiusLabel   = FindTMP(canvas, "radius");
        if (radiusLabel != null) radiusLabel.gameObject.SetActive(false);
        var settingsLabel = FindTMP(canvas, "idk");
        if (settingsLabel != null) settingsLabel.gameObject.SetActive(false);

        // ── Build the scrollable settings panel ────────────────────────────
        // Pass in the actual back/create button transforms so the scroll
        // bounds can be measured against them at runtime, instead of
        // hardcoded against canvas reference units that may or may not
        // match where the art team actually placed the buttons.
        BuildSettingsScroll(canvas,
            backBtn   != null ? backBtn.transform   : null,
            createBtn != null ? createBtn.transform : null);
    }

    // ── Action handlers ──────────────────────────────────────────────────────

    void OnCreateClicked()
    {
        var gm = GameManager.Instance;
        if (gm == null) return;
        // Keep the legacy flat fields populated for any caller that still
        // reads them directly, and ALSO populate pendingSettings with the
        // full override snapshot so the server can stamp it into the lobby.
        gm.pendingRadius        = _radius;
        gm.pendingImpostorCount = _impostors;
        gm.pendingTaskCount     = _tasks;
        gm.pendingSettings      = BuildOverrides();
        gm.CreateLobbyButton();
    }

    /// <summary>
    /// Pack the current control values into a wire-shape GameSettingsOverrides.
    /// Every field is non-null - the server treats null as "use my default",
    /// but since this UI exposes every field we always have a concrete value
    /// to ship. Time fields are exposed in seconds and converted to ms here.
    /// </summary>
    GameSettingsOverrides BuildOverrides()
    {
        return new GameSettingsOverrides
        {
            // Round shape
            MaxPlayers                    = _maxPlayers,
            ImpostorCount                 = _impostors,
            TaskCount                     = _tasks,
            // Distances (m)
            KillDistanceM                 = _killDist,
            ReportDistanceM               = _reportDist,
            TaskStartDistanceM            = _taskDist,
            MeetingArrivalRadiusM         = _meetingArrival,
            EmergencyMeetingCallRadiusM   = _emergencyCallRadius,
            RepairStationDistanceM        = _repairDist,
            // Cooldowns / counts
            KillCooldownMs                = _killCooldownS         * 1000,
            EmergencyMeetingCooldownMs    = _emergencyCooldownS     * 1000,
            MaxEmergencyMeetingsPerPlayer = _maxEmergencyMeetings,
            // Meeting phases (ms)
            ArrivalBaseMs                 = _arrivalBaseS           * 1000,
            AllowedLateMs                 = _allowedLateS           * 1000,
            DiscussionPhaseMs             = _discussionS            * 1000,
            VotingPhaseMs                 = _votingS                * 1000,
            // Sabotage
            SabotageCooldownMs            = _sabotageCooldownS      * 1000,
            CommsBlackoutDurationMs       = _commsDurationS         * 1000,
            CriticalMeltdownDeadlineMs    = _meltdownDeadlineS      * 1000,
            RepairStationHoldMs           = _repairHoldS            * 1000,
        };
    }

    // ── Settings ScrollRect ─────────────────────────────────────────────────

    /// <summary>
    /// Build the scrollable panel: ScrollRect → Viewport (RectMask2D) →
    /// Content (VerticalLayoutGroup + ContentSizeFitter). Each setting goes
    /// in as a row with a LayoutElement.preferredHeight so the VLG can stack
    /// them and the ContentSizeFitter can compute the scroll height.
    /// Pattern mirrors InGameHUDBuilder.BuildMeetingScroll.
    ///
    /// Bounds are MEASURED against the actual back/stvořit RectTransforms
    /// at runtime - not hardcoded against canvas reference units. The
    /// previous version pinned the scroll to 280..1700 in 1080x1920 ref
    /// space, which overlapped the create button on real layouts and made
    /// it un-tappable. We now place the scroll's bottom edge a fixed margin
    /// above stvořit's top edge, and its top edge a fixed margin below
    /// back's bottom edge, falling back to the old constants only if either
    /// button is missing.
    /// </summary>
    void BuildSettingsScroll(Transform canvas, Transform backT, Transform stvoritT)
    {
        // Force the canvas's layout to settle so GetWorldCorners returns the
        // real positioned rects rather than authored-time placeholders.
        Canvas.ForceUpdateCanvases();

        const float MARGIN = 40f; // px (in canvas reference units) above/below buttons

        var canvasRT = canvas as RectTransform;
        // Pivot offset so we can express Y as "from canvas bottom" rather
        // than the pivot-relative form InverseTransformPoint hands back.
        float pivotYOffset = canvasRT != null
            ? canvasRT.pivot.y * canvasRT.rect.height
            : 0f;

        float scrollBottomY = 280f;  // safe fallback if stvořit is missing
        float scrollTopY    = 1700f; // safe fallback if back is missing

        if (canvasRT != null && stvoritT is RectTransform stvoritRT)
        {
            var corners = new Vector3[4];
            stvoritRT.GetWorldCorners(corners); // [0]=BL, [1]=TL, [2]=TR, [3]=BR
            var topLocal = canvasRT.InverseTransformPoint(corners[1]);
            scrollBottomY = topLocal.y + pivotYOffset + MARGIN;
        }
        if (canvasRT != null && backT is RectTransform backRT)
        {
            var corners = new Vector3[4];
            backRT.GetWorldCorners(corners);
            var bottomLocal = canvasRT.InverseTransformPoint(corners[0]);
            scrollTopY = bottomLocal.y + pivotYOffset - MARGIN;
        }

        // Sanity-clamp: if either button is positioned weirdly (e.g. back
        // is below the create button, or they overlap), the computed
        // bounds would be a negative-height rect that draws as a 1px
        // sliver. Detect and fall back to the portrait-reference defaults.
        if (scrollTopY <= scrollBottomY + 200f)
        {
            Debug.LogWarning(
                $"[HostLobbyUI] Scroll bounds collapsed ({scrollBottomY:F0}..{scrollTopY:F0}); " +
                "falling back to 280..1700 portrait defaults.");
            scrollBottomY = 280f;
            scrollTopY    = 1700f;
        }

        var scrollRT = MakeRT("SettingsScroll", canvas);
        Anchor(scrollRT, new Vector2(0.05f, 0), new Vector2(0.95f, 0),
               new Vector2(0, scrollBottomY), new Vector2(0, scrollTopY));

        // Subtle dark backdrop so the scroll area visually separates from
        // the canvas's full-screen Panel underneath.
        var bgImg = scrollRT.gameObject.AddComponent<Image>();
        bgImg.color = new Color(0f, 0f, 0f, 0.55f);

        var sr = scrollRT.gameObject.AddComponent<ScrollRect>();

        var vp = MakeRT("Viewport", scrollRT);
        Stretch(vp);
        vp.gameObject.AddComponent<RectMask2D>();

        var content = MakeRT("Content", vp);
        content.anchorMin = new Vector2(0, 1);
        content.anchorMax = new Vector2(1, 1);
        content.pivot     = new Vector2(0.5f, 1);
        var vlg = content.gameObject.AddComponent<VerticalLayoutGroup>();
        vlg.childControlWidth      = true;
        vlg.childControlHeight     = false;
        vlg.childForceExpandWidth  = true;
        vlg.padding = new RectOffset(20, 20, 20, 20);
        vlg.spacing = 12;
        var csf = content.gameObject.AddComponent<ContentSizeFitter>();
        csf.verticalFit = ContentSizeFitter.FitMode.PreferredSize;

        sr.viewport = vp;
        sr.content  = content;
        sr.horizontal = false;
        sr.vertical   = true;
        sr.scrollSensitivity = 80;
        sr.movementType = ScrollRect.MovementType.Clamped;

        // ── Round shape ────────────────────────────────────────────────────
        AddHeader(content, "Round");
        AddSlider(content, "Game radius", 100, 2000, _radius,
                  v => _radius = v,
                  v => Mathf.RoundToInt(v) + " m");
        AddStepper(content, "Max players",      _maxPlayers, 4, 15, v => _maxPlayers = v);
        AddStepper(content, "Impostors",        _impostors,  1, 4,  v => _impostors  = v);
        AddStepper(content, "Tasks per crew",   _tasks,      1, 15, v => _tasks      = v);

        // ── Distances ──────────────────────────────────────────────────────
        AddHeader(content, "Distances (m)");
        AddStepper(content, "Kill",             _killDist,            1, 30, v => _killDist            = v);
        AddStepper(content, "Report",           _reportDist,          1, 30, v => _reportDist          = v);
        AddStepper(content, "Task start",       _taskDist,            1, 30, v => _taskDist            = v);
        AddStepper(content, "Meeting arrival",  _meetingArrival,      5, 50, v => _meetingArrival      = v);
        AddStepper(content, "Emergency call",   _emergencyCallRadius, 1, 30, v => _emergencyCallRadius = v);
        AddStepper(content, "Repair station",   _repairDist,          1, 30, v => _repairDist          = v);

        // ── Cooldowns ──────────────────────────────────────────────────────
        AddHeader(content, "Cooldowns (s)");
        AddStepper(content, "Kill cooldown",    _killCooldownS,        5, 120, v => _killCooldownS        = v);
        AddStepper(content, "Emergency cd",     _emergencyCooldownS,  10, 600, v => _emergencyCooldownS   = v);
        AddStepper(content, "Max emergencies",  _maxEmergencyMeetings, 0, 10,  v => _maxEmergencyMeetings = v);
        AddStepper(content, "Sabotage cd",      _sabotageCooldownS,   10, 600, v => _sabotageCooldownS    = v);

        // ── Meeting phases ─────────────────────────────────────────────────
        AddHeader(content, "Meeting phases (s)");
        AddStepper(content, "Arrival time",     _arrivalBaseS, 30, 300, v => _arrivalBaseS = v);
        AddStepper(content, "Allowed late",     _allowedLateS,  0, 120, v => _allowedLateS = v);
        AddStepper(content, "Discussion",       _discussionS,  10, 300, v => _discussionS  = v);
        AddStepper(content, "Voting",           _votingS,      10, 300, v => _votingS      = v);

        // ── Sabotage ───────────────────────────────────────────────────────
        AddHeader(content, "Sabotage (s)");
        AddStepper(content, "Comms duration",     _commsDurationS,    10, 600, v => _commsDurationS    = v);
        AddStepper(content, "Meltdown deadline",  _meltdownDeadlineS, 30, 600, v => _meltdownDeadlineS = v);
        AddStepper(content, "Repair hold",        _repairHoldS,        1, 60,  v => _repairHoldS       = v);
    }

    void AddHeader(RectTransform parent, string text)
    {
        var rt = MakeRT("Header_" + text, parent);
        var le = rt.gameObject.AddComponent<LayoutElement>();
        le.preferredHeight = HEADER_HEIGHT;
        le.minHeight       = HEADER_HEIGHT;

        var bg = rt.gameObject.AddComponent<Image>();
        bg.color = new Color(C_HEADER.r, C_HEADER.g, C_HEADER.b, 0.45f);

        var txtRT = MakeRT("Txt", rt);
        Stretch(txtRT);
        var tmp = txtRT.gameObject.AddComponent<TextMeshProUGUI>();
        tmp.text     = text;
        tmp.fontSize = 44;
        tmp.color    = C_TEXT;
        tmp.alignment = TextAlignmentOptions.MidlineLeft;
        tmp.fontStyle = FontStyles.Bold;
        tmp.margin    = new Vector4(20, 0, 20, 0);
    }

    void AddStepper(RectTransform parent, string label, int initial, int min, int max, System.Action<int> onChange)
    {
        var rt = MakeRT("Row_" + label, parent);
        var le = rt.gameObject.AddComponent<LayoutElement>();
        le.preferredHeight = ROW_HEIGHT;
        le.minHeight       = ROW_HEIGHT;
        BuildStepperRow(rt, label, initial, min, max, onChange);
    }

    void AddSlider(RectTransform parent, string label, float min, float max, float initial,
                   System.Action<float> onChange, System.Func<float, string> formatter)
    {
        var rt = MakeRT("Row_" + label, parent);
        var le = rt.gameObject.AddComponent<LayoutElement>();
        le.preferredHeight = SLIDER_HEIGHT;
        le.minHeight       = SLIDER_HEIGHT;

        // Top half: label (left) + value readout (right)
        var labelRT = MakeRT("Label", rt);
        Anchor(labelRT, new Vector2(0, 0.55f), new Vector2(0.7f, 1f), Vector2.zero, Vector2.zero);
        var lblTmp = labelRT.gameObject.AddComponent<TextMeshProUGUI>();
        lblTmp.text     = label;
        lblTmp.fontSize = 36;
        lblTmp.color    = C_TEXT;
        lblTmp.alignment = TextAlignmentOptions.MidlineLeft;
        lblTmp.fontStyle = FontStyles.Bold;

        var valRT = MakeRT("Val", rt);
        Anchor(valRT, new Vector2(0.7f, 0.55f), new Vector2(1f, 1f), Vector2.zero, Vector2.zero);
        var valTmp = valRT.gameObject.AddComponent<TextMeshProUGUI>();
        valTmp.text     = formatter(initial);
        valTmp.fontSize = 36;
        valTmp.color    = C_TEXT;
        valTmp.alignment = TextAlignmentOptions.MidlineRight;
        valTmp.fontStyle = FontStyles.Bold;

        // Bottom half: slider
        var sliderRT = MakeRT("Slider", rt);
        Anchor(sliderRT, new Vector2(0, 0.05f), new Vector2(1, 0.45f),
               Vector2.zero, Vector2.zero);
        var slider = sliderRT.gameObject.AddComponent<Slider>();
        slider.minValue = min;
        slider.maxValue = max;
        slider.value    = initial;
        BuildSliderVisuals(slider);
        slider.onValueChanged.AddListener(v =>
        {
            onChange?.Invoke(v);
            valTmp.text = formatter(v);
        });
    }

    // ── Stepper row: [Label]  [-]  [value]  [+]  ─────────────────────────────
    void BuildStepperRow(RectTransform parent, string label, int initial,
        int min, int max, System.Action<int> onChange)
    {
        int captured = Mathf.Clamp(initial, min, max);

        // Left half: label
        var lblRT = MakeRT("Label", parent);
        Anchor(lblRT, new Vector2(0, 0), new Vector2(0.55f, 1), Vector2.zero, Vector2.zero);
        var lblTmp = lblRT.gameObject.AddComponent<TextMeshProUGUI>();
        lblTmp.text     = label;
        lblTmp.fontSize = 32;
        lblTmp.color    = C_TEXT;
        lblTmp.alignment = TextAlignmentOptions.MidlineLeft;
        lblTmp.fontStyle = FontStyles.Bold;

        // Minus button
        var minusRT = MakeRT("Minus", parent);
        Anchor(minusRT, new Vector2(0.55f, 0.10f), new Vector2(0.69f, 0.90f), Vector2.zero, Vector2.zero);
        var minusBtn = MakeButton(minusRT, "-", C_BTN_BG);

        // Value label
        var valRT = MakeRT("Val", parent);
        Anchor(valRT, new Vector2(0.69f, 0), new Vector2(0.83f, 1), Vector2.zero, Vector2.zero);
        var valTmp = valRT.gameObject.AddComponent<TextMeshProUGUI>();
        valTmp.text     = captured.ToString();
        valTmp.fontSize = 40;
        valTmp.color    = C_TEXT;
        valTmp.alignment = TextAlignmentOptions.Center;
        valTmp.fontStyle = FontStyles.Bold;

        // Plus button
        var plusRT = MakeRT("Plus", parent);
        Anchor(plusRT, new Vector2(0.83f, 0.10f), new Vector2(0.97f, 0.90f), Vector2.zero, Vector2.zero);
        var plusBtn = MakeButton(plusRT, "+", C_BTN_HI);

        minusBtn.onClick.AddListener(() =>
        {
            captured = Mathf.Max(min, captured - 1);
            valTmp.text = captured.ToString();
            onChange?.Invoke(captured);
        });
        plusBtn.onClick.AddListener(() =>
        {
            captured = Mathf.Min(max, captured + 1);
            valTmp.text = captured.ToString();
            onChange?.Invoke(captured);
        });
    }

    Button MakeButton(RectTransform rt, string label, Color bg)
    {
        var img = rt.gameObject.AddComponent<Image>();
        img.color = bg;
        var btn = rt.gameObject.AddComponent<Button>();
        btn.targetGraphic = img;

        var txtRT = MakeRT("Txt", rt);
        Stretch(txtRT);
        var txtTmp = txtRT.gameObject.AddComponent<TextMeshProUGUI>();
        txtTmp.text     = label;
        txtTmp.fontSize = 52;
        txtTmp.color    = C_TEXT;
        txtTmp.alignment = TextAlignmentOptions.Center;
        txtTmp.fontStyle = FontStyles.Bold;
        return btn;
    }

    // ── Slider visual fill-in (Unity gives you no default if you AddComponent) ─
    void BuildSliderVisuals(Slider s)
    {
        var sRT = s.GetComponent<RectTransform>();

        var bgRT = MakeRT("Background", sRT);
        Stretch(bgRT);
        var bgImg = bgRT.gameObject.AddComponent<Image>();
        bgImg.color = C_TRACK;

        var fillArea = MakeRT("Fill Area", sRT);
        Anchor(fillArea, new Vector2(0, 0.30f), new Vector2(1, 0.70f),
               new Vector2(8, 0), new Vector2(-8, 0));
        var fillRT = MakeRT("Fill", fillArea);
        fillRT.anchorMin = Vector2.zero;
        fillRT.anchorMax = Vector2.one;
        fillRT.offsetMin = Vector2.zero;
        fillRT.offsetMax = Vector2.zero;
        var fillImg = fillRT.gameObject.AddComponent<Image>();
        fillImg.color = C_FILL;
        s.fillRect = fillRT;

        var handleArea = MakeRT("Handle Slide Area", sRT);
        Stretch(handleArea);
        var handleRT = MakeRT("Handle", handleArea);
        handleRT.sizeDelta        = new Vector2(40, 40);
        handleRT.anchorMin        = new Vector2(0, 0.5f);
        handleRT.anchorMax        = new Vector2(0, 0.5f);
        handleRT.anchoredPosition = Vector2.zero;
        var hImg = handleRT.gameObject.AddComponent<Image>();
        hImg.color = C_HANDLE;
        s.handleRect    = handleRT;
        s.targetGraphic = hImg;
    }

    // ── Scene-binding helpers ────────────────────────────────────────────────

    static Button FindButton(Transform root, string name)
    {
        var t = FindByName(root, name);
        return t != null ? t.GetComponent<Button>() : null;
    }

    static TMP_Text FindTMP(Transform root, string name)
    {
        var t = FindByName(root, name);
        return t != null ? t.GetComponent<TMP_Text>() : null;
    }

    static Transform FindByName(Transform root, string name)
    {
        if (root == null) return null;
        if (root.name == name) return root;
        foreach (Transform child in root)
        {
            var found = FindByName(child, name);
            if (found != null) return found;
        }
        return null;
    }

    // ── Layout helpers (shared with the injected controls) ───────────────────

    static RectTransform MakeRT(string name, Transform parent)
    {
        var go = new GameObject(name);
        var rt = go.AddComponent<RectTransform>();
        rt.SetParent(parent, false);
        rt.localScale = Vector3.one;
        return rt;
    }

    static void Stretch(RectTransform rt)
    {
        rt.anchorMin = Vector2.zero;
        rt.anchorMax = Vector2.one;
        rt.offsetMin = Vector2.zero;
        rt.offsetMax = Vector2.zero;
    }

    static void Anchor(RectTransform rt, Vector2 aMin, Vector2 aMax,
        Vector2 offMin, Vector2 offMax)
    {
        rt.anchorMin = aMin;
        rt.anchorMax = aMax;
        rt.offsetMin = offMin;
        rt.offsetMax = offMax;
    }
}
