/*
Crafter®.Graphics
Copyright (C) 2026 Catcrafts®
catcrafts.net

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 3.0 as published by the Free Software Foundation;

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
*/
module;
module Crafter.Graphics:InputField_impl;
import :InputField;
import :UI;
import :UIComponents;
import :Font;
import :Types;
import :Keys;
import std;

using namespace Crafter;

bool Crafter::InputField_IsValidCandidate(InputFieldType type, std::string_view s) {
    switch (type) {
        case InputFieldType::Text:
            return true;

        case InputFieldType::UInt: {
            for (char c : s) if (c < '0' || c > '9') return false;
            return true;
        }

        case InputFieldType::Int: {
            if (s.empty()) return true;
            std::size_t i = 0;
            if (s[0] == '-') {
                if (s.size() == 1) return true;       // lone '-' allowed mid-edit
                i = 1;
            }
            for (; i < s.size(); ++i)
                if (s[i] < '0' || s[i] > '9') return false;
            return true;
        }

        case InputFieldType::Float: {
            if (s.empty()) return true;
            std::size_t i = 0;
            if (s[0] == '-') {
                if (s.size() == 1) return true;
                i = 1;
            }
            bool sawDigit = false, sawDot = false, sawExp = false;
            for (; i < s.size(); ++i) {
                char c = s[i];
                if (c >= '0' && c <= '9') {
                    if (!sawExp) sawDigit = true;
                } else if (c == '.') {
                    if (sawDot || sawExp) return false;
                    sawDot = true;
                } else if (c == 'e' || c == 'E') {
                    if (sawExp || !sawDigit) return false;
                    sawExp = true;
                    if (i + 1 < s.size() && (s[i + 1] == '+' || s[i + 1] == '-')) ++i;
                } else {
                    return false;
                }
            }
            return true;
        }
    }
    return true;
}

void Crafter::InputField_OnText(InputField& f, std::string_view utf8) {
    if (utf8.empty()) return;
    std::string candidate = f.value;
    candidate.insert(f.cursorPos, utf8);
    if (!InputField_IsValidCandidate(f.type, candidate)) return;
    f.value = std::move(candidate);
    f.cursorPos += utf8.size();
}

void Crafter::InputField_OnKey(InputField& f, KeyCode code) {
    // `Key(...)` is consteval — every comparison below folds to an immediate
    // integer at compile time, so this is just a sequence of int-eq tests.
    if (code == Key(CrafterKeys::Backspace)) {
        if (f.cursorPos > 0 && !f.value.empty()) {
            f.value.erase(f.cursorPos - 1, 1);
            --f.cursorPos;
        }
    } else if (code == Key(CrafterKeys::Delete)) {
        if (f.cursorPos < f.value.size()) {
            f.value.erase(f.cursorPos, 1);
        }
    } else if (code == Key(CrafterKeys::Left)) {
        if (f.cursorPos > 0) --f.cursorPos;
    } else if (code == Key(CrafterKeys::Right)) {
        if (f.cursorPos < f.value.size()) ++f.cursorPos;
    } else if (code == Key(CrafterKeys::Home)) {
        f.cursorPos = 0;
    } else if (code == Key(CrafterKeys::End)) {
        f.cursorPos = f.value.size();
    }
}

std::size_t Crafter::InputField_HitTestCursor(const InputField& f,
                                              Rect rect,
                                              float clickX,
                                              Font& font, float fontSize,
                                              const InputFieldColors& colors)
{
    // Single O(n) cumulative-advance pass over the value, keyed at codepoint
    // byte boundaries — matching cursorPos, which is a byte offset. The old
    // loop re-walked the prefix for every boundary (O(n^2) glyph metric
    // lookups) and could even land mid-codepoint on multibyte input.
    float target = clickX - (rect.x + colors.paddingX);
    return font.NearestCursorByte(f.value, fontSize, target);
}

void Crafter::DrawInputField(UIBuffer& buf, const InputField& f, Rect rect,
                             Font& font, float fontSize,
                             const InputFieldColors& c,
                             bool caretVisible)
{
    if (buf.quads == nullptr || buf.quadCount == nullptr) return;

    const auto& bg     = f.focused ? c.bgFocused     : c.bg;
    const auto& border = f.focused ? c.borderFocused : c.border;

    if (*buf.quadCount < buf.quadCap) {
        buf.quads[(*buf.quadCount)++] = QuadItem{
            rect.x, rect.y, rect.w, rect.h,
            bg[0], bg[1], bg[2], bg[3],
            c.cornerRadius, c.cornerRadius, c.cornerRadius, c.cornerRadius,
            c.borderThickness, border[0], border[1], border[2],
        };
    }

    float baseline = rect.y + rect.h * 0.5f + fontSize * 0.32f;
    float textX    = rect.x + c.paddingX;
    DrawText(buf, f.value, textX, baseline, font, fontSize, c.text, TextAlign::Left);

    if (f.focused && caretVisible && *buf.quadCount < buf.quadCap) {
        std::string_view sub(f.value.data(), std::min(f.cursorPos, f.value.size()));
        float caretX = textX + (sub.empty() ? 0.0f : static_cast<float>(font.GetLineWidth(sub, fontSize)));
        float caretH = fontSize * 1.1f;
        float caretY = rect.y + (rect.h - caretH) * 0.5f;
        float caretW = std::max(1.0f, fontSize / 16.0f);
        buf.quads[(*buf.quadCount)++] = QuadItem{
            caretX, caretY, caretW, caretH,
            c.caret[0], c.caret[1], c.caret[2], c.caret[3],
            0, 0, 0, 0,
            0, 0, 0, 0,
        };
    }
}
