// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

#define DVX_WIDGET_IMPL
// widgetBox.c  -- VBox, HBox, and Frame container widgets
//
// VBox and HBox are the primary layout containers. They have no visual
// representation of their own  -- they exist purely to arrange children
// vertically or horizontally. The actual layout algorithm lives in
// widgetLayout.c (widgetCalcMinSizeBox / widgetLayoutBox) which handles
// weight-based space distribution, spacing, padding, and alignment.
//
// VBox and HBox are distinguished by a flag (WCLASS_HORIZ_CONTAINER) in
// the class table rather than having separate code. This keeps the layout
// engine unified  -- the same algorithm works in both orientations by
// swapping which axis is "major" vs "minor".
//
// Frame is a labeled grouping box with a Motif-style beveled border.
// It acts as a VBox for layout purposes (children stack vertically inside
// the frame's padded interior). The title text sits centered vertically
// on the top border line, with a small background-filled gap to visually
// "break" the border behind the title  -- this is the classic Win3.1/Motif
// group box appearance.

#include "dvxWgtP.h"

static int32_t sVBoxTypeId  = -1;
static int32_t sHBoxTypeId  = -1;
static int32_t sFrameTypeId = -1;

typedef enum {
    FrameInE,
    FrameOutE,
    FrameFlatE
} FrameStyleE;

typedef struct {
    const char *title;
    FrameStyleE style;
    uint32_t    color;
} FrameDataT;


// ============================================================
// Prototypes
// ============================================================

WidgetT *wgtFrame(WidgetT *parent, const char *title);
WidgetT *wgtHBox(WidgetT *parent);
void wgtRegister(void);
WidgetT *wgtVBox(WidgetT *parent);
void widgetFrameGetLayoutMetrics(const WidgetT *w, const BitmapFontT *font, int32_t *pad, int32_t *gap, int32_t *extraTop, int32_t *borderW);
void widgetFramePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);


WidgetT *wgtFrame(WidgetT *parent, const char *title) {
    WidgetT *w = widgetAllocWithText(parent, sFrameTypeId, sizeof(FrameDataT), title);

    if (w && w->data) {
        FrameDataT *fd = (FrameDataT *)w->data;
        fd->style = FrameInE;
    }

    return w;
}


WidgetT *wgtHBox(WidgetT *parent) {
    return widgetAlloc(parent, sHBoxTypeId);
}


WidgetT *wgtVBox(WidgetT *parent) {
    return widgetAlloc(parent, sVBoxTypeId);
}


void widgetFrameGetLayoutMetrics(const WidgetT *w, const BitmapFontT *font, int32_t *pad, int32_t *gap, int32_t *extraTop, int32_t *borderW) {
    FrameDataT *fd = (FrameDataT *)w->data;
    *pad      = DEFAULT_PADDING;
    *gap      = 0;
    *extraTop = font ? (font->charHeight / 2 + 2) : 0;
    *borderW  = (fd && fd->style == FrameFlatE) ? 1 : 2;
}


// Paint the frame border and optional title. The border is offset down by
// half the font height so the title text can sit centered on the top edge.
// This creates the illusion of the title "interrupting" the border  -- a
// background-colored rectangle is drawn behind the title to erase the
// border pixels, then the title is drawn on top.
//
// Three border styles are supported:
//   FrameFlatE  -- single-pixel solid color outline
//   FrameInE   -- Motif "groove" (inset bevel: shadow-then-highlight)
//   FrameOutE  -- Motif "ridge" (outset bevel: highlight-then-shadow)
// The groove/ridge are each two nested 1px bevels with swapped colors.
void widgetFramePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
    FrameDataT *fd = (FrameDataT *)w->data;
    int32_t  fb    = widgetFrameBorderWidth(w);
    int32_t  boxY  = w->y + font->charHeight / 2;
    int32_t  boxH  = w->h - font->charHeight / 2;
    uint32_t fg    = w->fgColor ? w->fgColor : colors->contentFg;
    uint32_t bg    = w->bgColor ? w->bgColor : colors->contentBg;

    if (fd->style == FrameFlatE) {
        // Flat: solid color rectangle outline
        uint32_t fc = fd->color ? fd->color : colors->windowShadow;

        drawHLine(d, ops, w->x, boxY, w->w, fc);
        drawHLine(d, ops, w->x, boxY + boxH - 1, w->w, fc);
        drawVLine(d, ops, w->x, boxY, boxH, fc);
        drawVLine(d, ops, w->x + w->w - 1, boxY, boxH, fc);
    } else {
        // Beveled groove/ridge: two nested 1px bevels
        BevelStyleT outer;
        BevelStyleT inner;

        if (fd->style == FrameInE) {
            outer.highlight = colors->windowShadow;
            outer.shadow    = colors->windowHighlight;
            inner.highlight = colors->windowHighlight;
            inner.shadow    = colors->windowShadow;
        } else {
            outer.highlight = colors->windowHighlight;
            outer.shadow    = colors->windowShadow;
            inner.highlight = colors->windowShadow;
            inner.shadow    = colors->windowHighlight;
        }

        outer.face  = BEVEL_NO_FILL;
        outer.width = 1;
        inner.face  = BEVEL_NO_FILL;
        inner.width = 1;
        drawBevel(d, ops, w->x, boxY, w->w, boxH, &outer);
        drawBevel(d, ops, w->x + 1, boxY + 1, w->w - 2, boxH - 2, &inner);
    }

    // Draw title centered vertically on the top border line
    if (fd->title && fd->title[0]) {
        int32_t titleW = textWidthAccel(font, fd->title);
        int32_t titleX = w->x + DEFAULT_PADDING + fb;
        int32_t titleY = boxY + (fb - font->charHeight) / 2;

        rectFill(d, ops, titleX - 2, titleY,
                 titleW + 4, font->charHeight, bg);

        drawWidgetTextAccel(d, ops, font, titleX, titleY, fd->title, fg, bg, true, w->enabled, colors);
    }
}


// ============================================================
// DXE registration
// ============================================================


static const WidgetClassT sClassVBox = {
    .version = WGT_CLASS_VERSION,
    .flags   = 0,
    .handlers = {
        [WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetCalcMinSizeBox,
        [WGT_METHOD_LAYOUT]        = (void *)widgetLayoutBox,
    }
};

static const WidgetClassT sClassHBox = {
    .version = WGT_CLASS_VERSION,
    .flags   = WCLASS_HORIZ_CONTAINER,
    .handlers = {
        [WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetCalcMinSizeBox,
        [WGT_METHOD_LAYOUT]        = (void *)widgetLayoutBox,
    }
};

static const WidgetClassT sClassFrame = {
    .version = WGT_CLASS_VERSION,
    .flags   = WCLASS_FOCUS_FORWARD | WCLASS_HAS_TEXT,
    .handlers = {
        [WGT_METHOD_PAINT]              = (void *)widgetFramePaint,
        [WGT_METHOD_GET_LAYOUT_METRICS] = (void *)widgetFrameGetLayoutMetrics,
        [WGT_METHOD_CALC_MIN_SIZE]      = (void *)widgetCalcMinSizeBox,
        [WGT_METHOD_LAYOUT]             = (void *)widgetLayoutBox,
    }
};

static const struct {
    WidgetT *(*vBox)(WidgetT *parent);
    WidgetT *(*hBox)(WidgetT *parent);
    WidgetT *(*frame)(WidgetT *parent, const char *title);
} sApi = {
    .vBox  = wgtVBox,
    .hBox  = wgtHBox,
    .frame = wgtFrame
};

// Separate API structs for each box type so the designer can
// create them independently. Each has create as the first slot.
static const struct { WidgetT *(*create)(WidgetT *); } sVBoxApi = { .create = wgtVBox };
static const struct { WidgetT *(*create)(WidgetT *); } sHBoxApi = { .create = wgtHBox };
static const struct { WidgetT *(*create)(WidgetT *, const char *); } sFrameApi = { .create = wgtFrame };

static const WgtIfaceT sIfaceVBox = {
    .basName      = "VBox",
    .props        = NULL,
    .propCount    = 0,
    .methods      = NULL,
    .methodCount  = 0,
    .events       = NULL,
    .eventCount   = 0,
    .createSig    = WGT_CREATE_PARENT,
    .isContainer  = true,
    .defaultEvent = "Click"
};

static const WgtIfaceT sIfaceHBox = {
    .basName      = "HBox",
    .props        = NULL,
    .propCount    = 0,
    .methods      = NULL,
    .methodCount  = 0,
    .events       = NULL,
    .eventCount   = 0,
    .createSig    = WGT_CREATE_PARENT,
    .isContainer  = true,
    .defaultEvent = "Click"
};

static const WgtIfaceT sIfaceFrame = {
    .basName      = "Frame",
    .props        = NULL,
    .propCount    = 0,
    .methods      = NULL,
    .methodCount  = 0,
    .events       = NULL,
    .eventCount   = 0,
    .createSig    = WGT_CREATE_PARENT_TEXT,
    .isContainer  = true,
    .defaultEvent = "Click"
};

void wgtRegister(void) {
    sVBoxTypeId  = wgtRegisterClass(&sClassVBox);
    sHBoxTypeId  = wgtRegisterClass(&sClassHBox);
    sFrameTypeId = wgtRegisterClass(&sClassFrame);

    // Original combined API (for widgetBox.h compatibility)
    wgtRegisterApi("box", &sApi);

    // Per-type APIs and ifaces (for designer/toolbox)
    wgtRegisterApi("vbox", &sVBoxApi);
    wgtRegisterIface("vbox", &sIfaceVBox);

    wgtRegisterApi("hbox", &sHBoxApi);
    wgtRegisterIface("hbox", &sIfaceHBox);

    wgtRegisterApi("frame", &sFrameApi);
    wgtRegisterIface("frame", &sIfaceFrame);
}
