from imgui_bundle import imgui


class LogView:
    def __init__(self):
        self.lines: list[str] = []
        self._need_scroll = False
        self._sel_start: int | None = None
        self._sel_end: int | None = None
        self._active = False

    def clear(self):
        self.lines.clear()
        self._sel_start = self._sel_end = None
        self._active = False

    def add(self, msg: str):
        self.lines.append(msg)
        self._need_scroll = True

    def _full_text(self):
        return "\n".join(self.lines)

    def _line_starts(self):
        starts = [0]
        for line in self.lines:
            starts.append(starts[-1] + len(line) + 1)
        return starts

    def _char_from_mouse(self, mx, my, ox, oy, lh, starts):
        li = int((my - oy) / lh) if lh > 0 else 0
        if li < 0 or li >= len(self.lines):
            return None
        line = self.lines[li]
        vx = mx - ox
        ci = len(line)
        for i in range(len(line)):
            w = imgui.calc_text_size(line[:i + 1]).x
            if vx < w:
                ci = i
                break
        return starts[li] + ci

    def draw(self, title: str):
        visible, _ = imgui.begin(title)
        if not visible:
            imgui.end()
            return

        io = imgui.get_io()
        line_height = imgui.get_text_line_height_with_spacing()
        line_starts = self._line_starts()

        imgui.begin_child("##body", imgui.ImVec2(0, 0),
                          imgui.ChildFlags_.none,
                          imgui.WindowFlags_.always_vertical_scrollbar | imgui.WindowFlags_.horizontal_scrollbar)

        origin = imgui.get_cursor_screen_pos()
        dl = imgui.get_window_draw_list()

        sel_col = imgui.color_convert_float4_to_u32(
            imgui.get_style().color_(imgui.Col_.text_selected_bg))

        sel_active = (self._sel_start is not None
                     and self._sel_end is not None
                     and self._sel_start != self._sel_end)

        # Render text via text_unformatted for proper layout
        for i, line in enumerate(self.lines):
            cursor = imgui.get_cursor_screen_pos()

            # Selection highlight (behind text)
            if sel_active:
                s0 = min(self._sel_start, self._sel_end)
                s1 = max(self._sel_start, self._sel_end)
                ls = line_starts[i]
                le = ls + len(line)
                if s0 < le and s1 > ls:
                    sli0 = max(0, s0 - ls)
                    sli1 = min(len(line), s1 - ls)
                    if sli0 < sli1:
                        before = imgui.calc_text_size(line[:sli0]).x
                        sel_w = imgui.calc_text_size(line[sli0:sli1]).x
                        dl.add_rect_filled(
                            imgui.ImVec2(cursor.x + before, cursor.y),
                            imgui.ImVec2(cursor.x + before + sel_w,
                                        cursor.y + line_height),
                            sel_col)

            imgui.text_unformatted(line)

        # Auto-scroll (after text so it targets the last line)
        if self._need_scroll:
            if not imgui.is_window_focused():
                imgui.set_scroll_here_y(1.0)
            self._need_scroll = False

        # Invisible button for mouse capture (covers full content)
        content_height = line_height * len(self.lines)
        if content_height > 0:
            imgui.set_cursor_pos(imgui.ImVec2(0, 0))
        imgui.invisible_button(
            "##capture", imgui.ImVec2(-1, content_height or -1),
            imgui.ButtonFlags_.mouse_button_left)

        # Selection logic
        if imgui.is_item_active():
            pos = self._char_from_mouse(
                io.mouse_pos.x, io.mouse_pos.y,
                origin.x, origin.y, line_height, line_starts)
            if not self._active:
                self._active = True
                self._sel_start = pos
                self._sel_end = pos
            elif pos is not None:
                self._sel_end = pos
        else:
            if self._active:
                self._active = False
                if (self._sel_start is not None
                        and self._sel_end is not None
                        and self._sel_start == self._sel_end):
                    self._sel_start = self._sel_end = None

        # Ctrl+C
        if ((imgui.is_item_hovered() or imgui.is_window_focused())
                and io.key_ctrl
                and imgui.is_key_pressed(imgui.Key.c)):
            if (self._sel_start is not None
                    and self._sel_end is not None
                    and self._sel_start != self._sel_end):
                s0 = min(self._sel_start, self._sel_end)
                s1 = max(self._sel_start, self._sel_end)
                imgui.set_clipboard_text(self._full_text()[s0:s1])

        imgui.end_child()
        imgui.end()
