// Main text entry engine rooted in papyrus

backingAllocator: std.mem.Allocator,
arena: std.heap.ArenaAllocator,
ctx: *papyrus.Context,
trg: ?*TextRenderGeometry = null,
cursorResults: ?TextRenderGeometry.HitResults = null,
selected: ?*papyrus.NodeProperty_TextEntry = null,
firstFrame: bool = false,
cursorBlink: bool = true,
cursorBlinkTime: f64 = 0.4,
cursorBlinkResetTime: f64 = 0.4,
insertIndex: u32 = 0,

const std = @import("std");
const papyrus = @import("papyrus.zig");
const NodeHandle = papyrus.NodeHandle;
const Context = papyrus.Context;
const core = @import("core");
const TextRenderGeometry = @import("textRender/textRenderGeometry.zig");

// couple of things we'll need to do...
pub fn create(ctx: *papyrus.Context, backingAllocator: std.mem.Allocator) !*@This() {
    const self = try backingAllocator.create(@This());

    self.* = .{
        .ctx = ctx,
        .backingAllocator = backingAllocator,
        .arena = std.heap.ArenaAllocator.init(backingAllocator),
    };

    return self;
}

pub fn sendCodePoint(self: *@This(), codepoint: u32) !void {
    const codepoints = @as([4]u8, @bitCast(codepoint));
    if (self.selected) |te| {
        te.editText.insert(self.insertIndex, codepoints[0]) catch unreachable;
        self.insertIndex += 1;
    }
}

pub fn tick(self: *@This(), deltaTime: f64) !void {
    if (self.firstFrame) {
        if (self.trg) |trg| {
            self.firstFrame = false;
            self.cursorResults = trg.getCurrentEndGeo();
        }
    }

    self.cursorBlinkTime -= deltaTime;

    if (self.cursorBlinkTime < 0) {
        self.cursorBlinkTime = self.cursorBlinkResetTime + self.cursorBlinkTime;
        self.cursorBlink = !self.cursorBlink;
    }

    if (self.trg) |trg| {
        self.cursorResults = trg.getGeometryAtIndex(self.insertIndex);
    }
}

pub fn testHits(self: *@This()) !void {
    if (self.trg) |trg| {
        const ctx = papyrus.getContext();
        const cursorPos = ctx.currentCursorPosition;
        self.cursorResults = trg.testHit(cursorPos);
        if (self.cursorResults) |hr| {
            try self.ctx.pushDebugText("text entry hittest found found: line={d} index={d}", .{ hr.line, hr.index });
            self.insertIndex = hr.index;
        }
    }
}

pub fn maybeResetSelection(self: *@This()) void {
    if (self.selected) |te| {
        te.entryState = .Normal;
    }
    self.selected = null;
    self.trg = null;
    self.cursorResults = null;
}

pub fn selectTextForEdit(self: *@This(), node: NodeHandle) void {
    self.maybeResetSelection();
    self.ctx.getTextEntry(node).entryState = .Pressed;
    self.selected = self.ctx.getTextEntry(node);
    self.insertIndex = if (self.insertIndex >= self.selected.?.editText.items.len) 0 else self.insertIndex;
    self.firstFrame = true;
    self.cursorBlink = true;
    self.cursorBlinkTime = self.cursorBlinkResetTime;
}

pub fn destroy(self: *@This()) void {
    self.arena.deinit();
    self.backingAllocator.destroy(self);
}

// ===== key events =====
pub fn sendEscape(self: *@This()) !void {
    // core.ui_logs("escape recieved");
    self.maybeResetSelection();
}

pub fn sendEnter(self: *@This()) !void {
    // core.ui_logs("enter recieved");

    if (self.selected) |te| {
        if (te.enterSendsNewline) {
            te.editText.insert(self.insertIndex, '\n') catch unreachable;
            self.insertIndex += 1;
        }
    }
}

pub fn sendTab(self: *@This()) !void {
    _ = self;
    // core.ui_logs("tab recieved");
}

pub fn sendBackspace(self: *@This()) !void {
    // core.ui_logs("backspace recieved");
    if (self.selected) |te| {
        if (self.insertIndex == 0) {
            return;
        } else {
            _ = te.editText.orderedRemove(@max(self.insertIndex - 1, 0));
            self.insertIndex -= 1;
        }
    }
}

pub fn sendDelete(self: *@This()) !void {
    // core.ui_logs("delete recieved");
    if (self.selected) |te| {
        if (self.insertIndex >= te.editText.items.len) {
            return;
        } else {
            _ = te.editText.orderedRemove(self.insertIndex);
        }
    }
}

pub fn sendRight(self: *@This()) !void {
    // core.ui_logs("right recieved");

    if (self.selected) |te| {
        if (self.insertIndex < te.editText.items.len) {
            self.insertIndex = @min(self.insertIndex + 1, te.editText.items.len);
        }
    }
    self.cursorBlinkTime = self.cursorBlinkResetTime;
    self.resetCursorBlink();
}

pub fn sendLeft(self: *@This()) !void {
    // core.ui_logs("left recieved");
    if (self.selected) |te| {
        _ = te;
        if (self.insertIndex > 0) {
            self.insertIndex = self.insertIndex - 1;
        }
    }
    self.cursorBlinkTime = self.cursorBlinkResetTime;
    self.resetCursorBlink();
}

fn cursorJumpRelative(self: *@This(), offset: core.Vector2f) !void {
    if (self.selected) |te| {
        _ = te;
        if (self.trg) |trg| {
            if (self.cursorResults) |cr| {
                const jumpTo = cr.characterGeo.pos.add(offset).add(.{ .x = 1.0 });
                if (trg.testHit(jumpTo)) |newCr| {
                    self.cursorResults = newCr;
                    self.insertIndex = newCr.index;
                } else {}
            } else {}
        } else {}
    }
}

pub fn sendUp(self: *@This()) !void {
    // core.ui_logs("up recieved");
    if (self.cursorResults) |cr| {
        _ = cr;
        self.cursorJumpRelative(.{ .x = 1.0, .y = self.cursorResults.?.characterGeo.size.y * 1.5 }) catch unreachable;
    }
    self.resetCursorBlink();
}

pub fn sendDown(self: *@This()) !void {
    // core.ui_logs("down recieved");
    if (self.cursorResults) |cr| {
        _ = cr;
        self.cursorJumpRelative(.{ .x = 1.0, .y = -self.cursorResults.?.characterGeo.size.y * 0.5 }) catch unreachable;
    }
    self.resetCursorBlink();
}

inline fn resetCursorBlink(self: *@This()) void {
    self.cursorBlinkTime = self.cursorBlinkResetTime;
    self.cursorBlink = true;
}

pub fn sendPageup(self: *@This()) !void {
    _ = self;
    // core.ui_logs("pageup recieved");
}

pub fn sendPagedown(self: *@This()) !void {
    _ = self;
    // core.ui_logs("pagedown recieved");
}

pub fn sendHome(self: *@This()) !void {
    _ = self;
    // core.ui_logs("home recieved");
}

pub fn sendEnd(self: *@This()) !void {
    _ = self;
    // core.ui_logs("end recieved");
}
