// 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
// widgetWrapBox.c -- Flow/wrap layout container
//
// Lays out children left-to-right, wrapping to the next row when
// the available width is exceeded. Each row's height is the max
// child height in that row. Supports spacing between items and
// rows. Supports per-row alignment (center, right) for short rows.

#include "dvxWgtP.h"

#include <stdlib.h>
#include <string.h>

// ============================================================
// Constants
// ============================================================

#define DEFAULT_PAD     2
#define DEFAULT_GAP     4

// ============================================================
// Type ID and per-instance data
// ============================================================

static int32_t sTypeId = -1;

typedef struct {
    int32_t wrappedH; // actual height from last layout pass (-1 = not yet computed)
} WrapBoxDataT;


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

void widgetWrapBoxCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetWrapBoxLayout(WidgetT *w, const BitmapFontT *font);
void widgetWrapBoxPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
static WidgetT *wrapBoxCreate(WidgetT *parent);
static int32_t wrapBoxGetAlign(const WidgetT *w);
static void wrapBoxSetAlign(WidgetT *w, int32_t align);


// Measure/layout cycle: the wrapped height depends on the allocated
// width, which isn't known during the measure pass. On the first
// measure, we report one row's height. The layout pass computes
// the actual wrapped height and stores it in WrapBoxDataT.wrappedH.
// On subsequent measures (triggered by resize or ScrollPane), we
// return the stored height so the parent sees the real content size.

void widgetWrapBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
    int32_t pad  = wgtResolveSize(w->padding, 0, font->charWidth);
    int32_t maxChildW = 0;
    int32_t maxChildH = 0;

    if (pad == 0) {
        pad = DEFAULT_PAD;
    }

    for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
        if (!c->visible) {
            continue;
        }

        widgetCalcMinSizeTree(c, font);

        if (c->calcMinW > maxChildW) {
            maxChildW = c->calcMinW;
        }

        if (c->calcMinH > maxChildH) {
            maxChildH = c->calcMinH;
        }
    }

    w->calcMinW = maxChildW + pad * 2;

    WrapBoxDataT *d = (WrapBoxDataT *)w->data;

    if (d && d->wrappedH > 0) {
        w->calcMinH = d->wrappedH;
    } else {
        w->calcMinH = maxChildH + pad * 2;
    }
}


// Three-pass layout:
//   1. Place children left-to-right with wrapping
//   2. Apply per-row alignment offsets
//   3. Recurse into child containers (so children see final positions)

void widgetWrapBoxLayout(WidgetT *w, const BitmapFontT *font) {
    int32_t pad = wgtResolveSize(w->padding, 0, font->charWidth);
    int32_t gap = wgtResolveSize(w->spacing, 0, font->charWidth);

    if (pad == 0) {
        pad = DEFAULT_PAD;
    }

    if (gap == 0) {
        gap = DEFAULT_GAP;
    }

    int32_t availW = w->w - pad * 2;
    int32_t baseX  = w->x + pad;

    // Pass 1: position children left-aligned with wrapping
    int32_t curX = baseX;
    int32_t curY = w->y + pad;
    int32_t rowH = 0;

    for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
        if (!c->visible) {
            continue;
        }

        int32_t childW = c->calcMinW;
        int32_t childH = c->calcMinH;

        if (curX > baseX && (curX - baseX) + childW > availW) {
            curX  = baseX;
            curY += rowH + gap;
            rowH  = 0;
        }

        c->x = curX;
        c->y = curY;
        c->w = childW;
        c->h = childH;

        if (childH > rowH) {
            rowH = childH;
        }

        curX += childW + gap;
    }

    // Pass 2: apply alignment offsets per row
    if (w->align == AlignCenterE || w->align == AlignEndE) {
        WidgetT *rowStart = NULL;
        int32_t  rowY     = -1;
        int32_t  rowEndX  = 0;

        for (WidgetT *c = w->firstChild; ; c = c ? c->nextSibling : NULL) {
            bool newRow = false;

            if (!c) {
                newRow = (rowStart != NULL);
            } else if (c->visible && c->y != rowY && rowStart != NULL) {
                newRow = true;
            }

            if (newRow) {
                int32_t rowW  = rowEndX - baseX;
                int32_t slack = availW - rowW;

                if (slack > 0) {
                    int32_t offset = (w->align == AlignCenterE) ? slack / 2 : slack;

                    for (WidgetT *r = rowStart; r != c; r = r->nextSibling) {
                        if (r->visible && r->y == rowY) {
                            r->x += offset;
                        }
                    }
                }

                rowStart = NULL;
            }

            if (!c) {
                break;
            }

            if (!c->visible) {
                continue;
            }

            if (rowStart == NULL || c->y != rowY) {
                rowStart = c;
                rowY     = c->y;
            }

            rowEndX = c->x + c->w;
        }
    }

    // Pass 3: recurse into child containers AFTER alignment
    // so children see their parent's final position.
    for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
        if (c->visible) {
            widgetLayoutChildren(c, font);
        }
    }

    // Store actual wrapped height for subsequent measure passes.
    int32_t usedH = (curY + rowH + pad) - w->y;
    WrapBoxDataT *d = (WrapBoxDataT *)w->data;

    if (d) {
        d->wrappedH = usedH;
    }

    if (usedH > w->calcMinH) {
        w->calcMinH = usedH;
    }
}


void widgetWrapBoxPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
    (void)w;
    (void)disp;
    (void)ops;
    (void)font;
    (void)colors;
}


static WidgetT *wrapBoxCreate(WidgetT *parent) {
    if (!parent) {
        return NULL;
    }

    WidgetT *w = widgetAlloc(parent, sTypeId);

    if (w) {
        WrapBoxDataT *d = (WrapBoxDataT *)calloc(1, sizeof(WrapBoxDataT));

        if (d) {
            d->wrappedH = -1;
        }

        w->data = d;
    }

    return w;
}


static int32_t wrapBoxGetAlign(const WidgetT *w) {
    return (int32_t)w->align;
}


static void wrapBoxSetAlign(WidgetT *w, int32_t align) {
    w->align = (WidgetAlignE)align;
    wgtInvalidate(w);
}


static const char *sAlignNames[] = { "Left", "Center", "Right", NULL };

static const WgtPropDescT sProps[] = {
    { "Alignment", WGT_IFACE_ENUM, (void *)wrapBoxGetAlign, (void *)wrapBoxSetAlign, sAlignNames }
};


static const WidgetClassT sClass = {
    .version  = WGT_CLASS_VERSION,
    .flags    = 0,
    .handlers = {
        [WGT_METHOD_PAINT]         = (void *)widgetWrapBoxPaint,
        [WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetWrapBoxCalcMinSize,
        [WGT_METHOD_LAYOUT]        = (void *)widgetWrapBoxLayout,
    }
};


static const struct {
    WidgetT *(*create)(WidgetT *parent);
} sApi = {
    .create = wrapBoxCreate
};


static const WgtIfaceT sIface = {
    .basName      = "WrapBox",
    .props        = sProps,
    .propCount    = 1,
    .methods      = NULL,
    .methodCount  = 0,
    .events       = NULL,
    .eventCount   = 0,
    .createSig    = WGT_CREATE_PARENT,
    .isContainer  = true,
    .defaultEvent = "Click",
    .namePrefix   = "WrapBox"
};


void wgtRegister(void) {
    sTypeId = wgtRegisterClass(&sClass);
    wgtRegisterApi("wrapbox", &sApi);
    wgtRegisterIface("wrapbox", &sIface);
}
