"""
Data table components with sorting, filtering, and pagination support.
"""
from .base import Component
from typing import List, Optional, Callable, Any, Literal
from .utils import update_url, table_page_range
import logging
import html
from .fields import TextField
from .forms import Button

logger = logging.getLogger(__name__)


class TableColumn(Component):
    """
    Used for sorting
    """

    def __init__(self, label: Optional[str] = None, orderable: bool = False, **kwargs):
        super().__init__(**kwargs)
        self.children = (
            self.children
            if self.children is not None
            else [TextField(uid=f"{self.uid}_default_field" if self.uid else "")]
        )
        self.label = self.key.title() if label is None else label
        self.orderable = orderable

    def render_html(self, **kwargs) -> str:
        request = kwargs["request"]
        content = (
            f"""
            <a href="{update_url(request.path, sort=self.key)}"
                hx-get="{update_url(request.path, sort=self.key)}"
                hx-target="closest .data-table-container" hx-swap="morph" hx-push-url="false"
                class="hover:text-primary">
                {self.label}
            </a>
            """
            if self.orderable
            else f"""
                {self.label}
            """
        )
        return f"""<th id='{self.uid}' scope="col" class="whitespace-nowrap min-w-[100px]">{content}</th>"""


def get_default_displays():
    return {
        "List": TableListContent,
        "Grid": TableGridContent,
    }


class Table(Component):
    """A comprehensive data table component with support for multiple views (Grid/List)."""
    def __init__(
        self,
        columns: List[TableColumn],
        scripts: Optional[List[str]] = None,
        url: Optional[Callable | str] = None,
        title: str = "",
        subtitle: str = "",
        displays: Optional[dict[str, Component]] = None,
        filter_component: Optional[Component] = None,
        create_url: Optional[str | Callable] = None,
        select: Literal["single", "multi"] | None = None,
        value_key: str = "pk",
        display_key: str = "__str__",
        **kwargs,
    ):
        displays = displays if displays is not None else get_default_displays()

        super().__init__(**kwargs)
        self.columns = columns
        self.scripts = scripts or []
        self.title = title
        self.subtitle = subtitle
        self.displays = displays
        self.filter_component = filter_component
        self.create_url = create_url
        self.url = url
        self.select = select
        self.value_key = value_key
        self.display_key = display_key

        self.children = [
            TableHeader(
                title=title,
                subtitle=subtitle,
                filter_component=filter_component,
                url=create_url,
                displays=displays,
                uid=f"{self.uid}_header" if self.uid else "",
                table_uid=self.uid,
                select=select,
            ),
            TableDisplaysContent(
                displays=displays,
                columns=columns,
                key=self.key,
                uid=f"{self.uid}_display_content" if self.uid else "",
                url=self.url,
                table_uid=self.uid,
                select=select,
                value_key=value_key,
                display_key=display_key,
            ),
        ]

    def _render_selection_script(self, target_input: str, modal_id: str) -> str:
        """Render the selection helper script for single/multi select modes."""
        if not self.select:
            return ""

        helper_name = f"TableSelection_{self.uid}".replace("-", "_")
        target_input_escaped = html.escape(target_input)
        modal_id_escaped = html.escape(modal_id)

        return f"""
            <script>
            (function() {{
                try {{
                    var helper = window.{helper_name} = {{}};
                    var targetInput = '{target_input_escaped}';
                    var modalId = '{modal_id_escaped}';

                    helper.selectSingle = function(value, display) {{
                        try {{
                            var valueEl = document.getElementById(targetInput + '_value');
                            var displayEl = document.getElementById(targetInput + '_display');
                            if (valueEl) {{
                                valueEl.value = value;
                            }} else {{
                                console.error('{helper_name}.selectSingle: value element not found:', targetInput + '_value');
                            }}
                            if (displayEl) {{
                                displayEl.textContent = display;
                                displayEl.classList.remove('text-base-content/50');
                            }} else {{
                                console.error('{helper_name}.selectSingle: display element not found:', targetInput + '_display');
                            }}
                            helper.closeModal();
                        }} catch (e) {{
                            console.error('{helper_name}.selectSingle error:', e);
                        }}
                    }};

                    helper.addItem = function(value, display) {{
                        try {{
                            var container = document.getElementById(targetInput + '_items');
                            if (!container) {{
                                console.error('{helper_name}.addItem: container not found:', targetInput + '_items');
                                return;
                            }}
                            var itemId = targetInput + '_item_' + value;
                            if (document.getElementById(itemId)) {{
                                return;
                            }}

                            var item = document.createElement('div');
                            item.id = itemId;
                            item.className = 'flex items-center gap-2 bg-base-200 rounded-lg px-3 py-1';

                            var input = document.createElement('input');
                            input.type = 'hidden';
                            input.name = targetInput + '_values';
                            input.value = value;

                            var span = document.createElement('span');
                            span.className = 'text-sm truncate max-w-[200px]';
                            span.textContent = display;

                            var closeBtn = document.createElement('button');
                            closeBtn.type = 'button';
                            closeBtn.className = 'btn btn-ghost btn-xs';
                            closeBtn.innerHTML = `{{% heroicon_mini "x-mark" class="w-3 h-3" %}}`;
                            closeBtn.onclick = function(e) {{
                                e.stopPropagation();
                                helper.removeItem(value);
                            }};

                            item.appendChild(input);
                            item.appendChild(span);
                            item.appendChild(closeBtn);
                            container.appendChild(item);
                        }} catch (e) {{
                            console.error('{helper_name}.addItem error:', e);
                        }}
                    }};

                    helper.removeItem = function(value) {{
                        try {{
                            var item = document.getElementById(targetInput + '_item_' + value);
                            if (item) {{
                                item.remove();
                            }}
                            var checkboxes = document.querySelectorAll('[data-selection-value="' + value + '"]');
                            checkboxes.forEach(function(cb) {{
                                cb.checked = false;
                            }});
                        }} catch (e) {{
                            console.error('{helper_name}.removeItem error:', e);
                        }}
                    }};

                    helper.isSelected = function(value) {{
                        try {{
                            return !!document.getElementById(targetInput + '_item_' + value);
                        }} catch (e) {{
                            console.error('{helper_name}.isSelected error:', e);
                            return false;
                        }}
                    }};

                    helper.toggleItem = function(value, display, checked) {{
                        try {{
                            if (checked) {{
                                helper.addItem(value, display);
                            }} else {{
                                helper.removeItem(value);
                            }}
                        }} catch (e) {{
                            console.error('{helper_name}.toggleItem error:', e);
                        }}
                    }};

                    helper.closeModal = function() {{
                        try {{
                            if (modalId) {{
                                var modal = document.getElementById(modalId);
                                if (modal) {{
                                    modal.remove();
                                }} else {{
                                    console.error('{helper_name}.closeModal: modal not found:', modalId);
                                }}
                            }}
                        }} catch (e) {{
                            console.error('{helper_name}.closeModal error:', e);
                        }}
                    }};
                }} catch (e) {{
                    console.error('{helper_name} initialization error:', e);
                }}
            }})();
            </script>
        """

    def render_html(self, **kwargs) -> str:
        target_input = kwargs.get("target_input", "")
        modal_id = kwargs.get("modal_id", "")

        scripts = "\n".join([f"<script>{script}</script>" for script in self.scripts])
        selection_script = self._render_selection_script(target_input, modal_id)
        header = self.children[0].render(**kwargs)
        displays = self.children[1].render(**kwargs)
        first_view = next(iter(self.displays.keys()), "List")
        return f"""
            {scripts}
            {selection_script}
            <div id="{self.uid}"
                class="w-full data-table-container {self.classes}"
                x-data="{{ view: '{first_view}' }}">
                    {header}
                    {displays}
            </div>
        """


class TableHeader(Component):
    def __init__(
        self,
        filter_component: Optional[Component],
        displays: Optional[dict[str, Component]],
        table_uid: str,
        title: str = "",
        subtitle: str = "",
        select: Literal["single", "multi"] | None = None,
        **kwargs,
    ):
        super().__init__(**kwargs)
        self.title = title
        self.subtitle = subtitle
        self.filter_component = filter_component
        self.displays = displays or {}
        self.table_uid = table_uid
        self.select = select

    def render_html(self, **kwargs) -> str:
        modal_id = kwargs.get("modal_id", "")

        title = f"<div id='{self.uid}_title' class='text-xl font-semibold'>{self.title}</div>"
        subtitle = f"<div id='{self.uid}_subtitle' class='text-sm text-gray-500'>{self.subtitle}</div>"
        filter_component = (
            f"""
            <details id='{self.uid}_filter' class="dropdown dropdown-end" @click.outside="$el.removeAttribute('open')">
                <summary class="btn btn-square dropdown-toggle btn-primary btn-sm">
                    {{% heroicon_mini "funnel" %}}
                </summary>
                <div
                    class="card w-64 my-1.5 card-body shadow dropdown-content border border-base-300 rounded-box z-2 bg-base-100">
                    {self.filter_component.render(**kwargs)}
                </div>
            </details>
        """
            if self.filter_component
            else ""
        )

        create_url = self.get_url(**kwargs)

        # Hide create button in selection mode
        create_url_link = ""
        if not self.select and create_url:
            create_url_link = f"""
                <a id='{self.uid}_create' href='{create_url}' class='btn btn-square btn-outline btn-sm'
                    hx-get="{create_url}" hx-target="#app-layout" hx-swap="outerHTML">
                    {{% heroicon_mini "plus" %}}
                </a>
            """


        displays = TableDisplays(
            self.displays,
            uid=f"{self.uid}_displays",
            table_uid=self.table_uid,
        ).render(**kwargs)

        return f"""
            <div id='{self.uid}' class='flex justify-between items-center relative my-2'>
                {f"<div> {title} {subtitle} </div>"}
                <div class="flex items-center gap-2">
                    {displays}
                    {filter_component}
                    {create_url_link}
                </div>
            </div>
        """


class TableDisplays(Component):
    def __init__(self, displays: dict[str, Component], table_uid: str, **kwargs):
        super().__init__(**kwargs)
        self.displays = displays
        self.table_uid = table_uid

    def render_html(self, **kwargs) -> str:
        options = "".join(
            f"<option value='{name}'>{name}</option>" for name in self.displays.keys()
        )
        return f"""
            <select id='{self.uid}' class="select select-md" x-model="view">
                {options}
            </select>
        """


class TablePagination(Component):
    def __init__(self, table_uid: str, **kwargs):
        super().__init__(**kwargs)
        self.table_uid = table_uid

    def render_html(self, **kwargs) -> str:
        page = self.get_value(**kwargs)
        request = kwargs["request"]

        page_range = table_page_range(page)

        pages = ""

        for i, section in enumerate(page_range):
            for p in section:
                pages += Button(
                    uid=f"{self.uid}_page_{p}",
                    label=str(p),
                    url=update_url(
                        request.get_full_path(),
                        **{"page": p},
                    ),
                    target=f"#{self.table_uid}_display_content",
                    push_url=False,
                    classes="join-item btn-sm"
                    + (" btn-active" if page.number == p else ""),
                ).render(**kwargs)
            if i < len(page_range) - 1:
                pages += Button(
                    uid=f"{self.uid}_page_{p}_sep",
                    label="...",
                    disabled=True,
                    classes="join-item btn-sm",
                ).render(**kwargs)

        return (
            f"""
            <div id='{
                self.uid
            }' class="flex flex-col justify-center items-center gap-2 p-4">
                <div class="join">
                    {pages}
                </div>
            </div>
        """
            if page.paginator.num_pages > 1
            else ""
        )


class TableDisplaysContent(Component):
    def __init__(
        self,
        displays: dict[str, Component],
        columns: List[Component],
        table_uid: str,
        select: Literal["single", "multi"] | None = None,
        value_key: str = "pk",
        display_key: str = "__str__",
        key: Optional[str] = None,
        **kwargs,
    ):
        super().__init__(key=key, **kwargs)
        self.displays = displays
        self.columns = columns
        self.table_uid = table_uid
        self.select = select
        self.value_key = value_key
        self.display_key = display_key

    def render_html(self, **kwargs) -> str:
        pagination = TablePagination(
            key=self.key,
            uid=f"{self.uid}_pagination",
            table_uid=self.table_uid,
        ).render(**kwargs)

        display_html_parts = []
        for display_name, display_component in self.displays.items():
            component = display_component(
                self.columns,
                key=self.key,
                url=self.url,
                uid=f"{self.uid}_{display_name}",
                table_uid=self.table_uid,
                select=self.select,
                value_key=self.value_key,
                display_key=self.display_key,
            )
            display_html_parts.append(
                f"<div id='{self.uid}_{display_name}' x-show='view === \"{display_name}\"'>{component.render(**kwargs)}</div>"
            )

        displays_html = "\n".join(display_html_parts)
        return f"""
            <div id='{self.uid}' class="relative my-2">
                {displays_html}
                {pagination}
            </div>
        """


class TableListContent(Component):
    def __init__(
        self,
        columns: List[TableColumn],
        table_uid: str = "",
        select: Literal["single", "multi"] | None = None,
        value_key: str = "pk",
        display_key: str = "__str__",
        **kwargs,
    ):
        super().__init__(**kwargs)
        self.columns = columns
        self.table_uid = table_uid
        self.select = select
        self.value_key = value_key
        self.display_key = display_key

    def get_url(self, row: Any) -> Optional[str]:
        if hasattr(self.url, "__call__"):
            return self.url(row)
        return self.url

    def _get_value(self, row: Any) -> str:
        return str(getattr(row, self.value_key, row.pk))

    def _get_display_value(self, row: Any) -> str:
        if self.display_key == "__str__":
            return str(row)
        return str(getattr(row, self.display_key, str(row)))

    def _render_row_cells(self, row: Any, **kwargs) -> str:
        cells = ""
        for column in self.columns:
            if not column.check(**kwargs):
                continue
            cells += "<td class='whitespace-nowrap truncate max-w-xs min-w-[100px]'>"
            for component in column.children:
                cells += component.render(**kwargs, object=row)
            cells += "</td>"
        return cells

    def _render_header(self, **kwargs) -> str:
        th_parts = []
        if self.select == "multi":
            th_parts.append("<th class='w-10'></th>")
        for column in self.columns:
            th_parts.append(column.render(uid=f"{self.uid}_{column.key}", **kwargs))
        return "\n".join(th_parts)

    def _render_empty_row(self) -> str:
        colspan = len(self.columns) + (1 if self.select == "multi" else 0)
        return f"""
            <tr>
                <td colspan='{colspan}' class='text-center text-base-content/50 py-8'>
                    Table is empty
                </td>
            </tr>
        """

    def _render_single_select_row(self, row: Any, **kwargs) -> str:
        value = html.escape(self._get_value(row))
        display = html.escape(self._get_display_value(row))
        helper_name = f"TableSelection_{self.table_uid}".replace("-", "_")
        onclick = f"{helper_name}.selectSingle('{value}', '{display}')"

        return f"""
            <tr id='{self.uid}_{row.pk}'
                class="cursor-pointer hover:bg-primary/10 transition-colors"
                onclick="try {{ {onclick} }} catch(e) {{ console.error('Selection error:', e); }}">
                {self._render_row_cells(row, **kwargs)}
            </tr>
        """

    def _render_multi_select_row(self, row: Any, target_input: str, **kwargs) -> str:
        value = html.escape(self._get_value(row))
        display = html.escape(self._get_display_value(row))
        helper_name = f"TableSelection_{self.table_uid}".replace("-", "_")
        target_input_escaped = html.escape(target_input)

        return f"""
            <tr id='{self.uid}_{row.pk}' class="hover:bg-primary/10 transition-colors">
                <td class='w-10'>
                    <input type="checkbox"
                           class="checkbox checkbox-primary"
                           data-selection-value="{value}"
                           x-init="$el.checked = !!document.getElementById('{target_input_escaped}_item_{value}')"
                           onchange="try {{ {helper_name}.toggleItem('{value}', '{display}', this.checked) }} catch(e) {{ console.error('Toggle error:', e); }}" />
                </td>
                {self._render_row_cells(row, **kwargs)}
            </tr>
        """

    def _render_url_row(self, row: Any, **kwargs) -> str:
        url = self.get_url(row)
        return f"""
            <tr id='{self.uid}_{row.pk}' href="{url}" hx-get="{url}" hx-target="#app-layout" class="cursor-pointer hover:bg-base-content/20" hx-push-url="true">
                {self._render_row_cells(row, **kwargs)}
            </tr>
        """

    def _render_static_row(self, row: Any, **kwargs) -> str:
        return f"""
            <tr id='{self.uid}_{row.pk}'>
                {self._render_row_cells(row, **kwargs)}
            </tr>
        """

    def render_html(self, **kwargs) -> str:
        target_input = kwargs.get("target_input", "")

        th = self._render_header(**kwargs)
        data = self.get_value(**kwargs)
        objects = data.object_list if hasattr(data, "object_list") else data

        tbody = ""
        if len(objects) == 0:
            tbody = self._render_empty_row()
        elif self.select == "single":
            for row in objects:
                tbody += self._render_single_select_row(row, **kwargs)
        elif self.select == "multi":
            for row in objects:
                tbody += self._render_multi_select_row(row, target_input, **kwargs)
        elif self.url:
            for row in objects:
                tbody += self._render_url_row(row, **kwargs)
        else:
            for row in objects:
                tbody += self._render_static_row(row, **kwargs)

        return f"""
            <div id='{self.uid}' class="table-container flex flex-col rounded-box border border-base-content/10 bg-base-100">
                <div class="overflow-x-auto">
                <table class="table table-zebra">
                    <thead>
                        <tr>
                            {th}
                        </tr>
                    </thead>
                    <tbody>
                        {tbody}
                    </tbody>
                </table>
                </div>
            </div>
        """


class TableGridContent(Component):
    def __init__(
        self,
        columns: List[TableColumn],
        table_uid: str = "",
        select: Literal["single", "multi"] | None = None,
        value_key: str = "pk",
        display_key: str = "__str__",
        **kwargs,
    ):
        super().__init__(**kwargs)
        self.columns = columns
        self.table_uid = table_uid
        self.select = select
        self.value_key = value_key
        self.display_key = display_key

    def get_url(self, row: Any) -> Optional[str]:
        if hasattr(self.url, "__call__"):
            if row is not None:
                return self.url(row)
            return None
        return self.url

    def _get_value(self, row: Any) -> str:
        return str(getattr(row, self.value_key, row.pk))

    def _get_display_value(self, row: Any) -> str:
        if self.display_key == "__str__":
            return str(row)
        return str(getattr(row, self.display_key, str(row)))

    def _render_card_content(self, row: Any, **kwargs) -> str:
        content = "<div class='font-semibold text-md truncate'>"
        if self.columns:
            for component in self.columns[0].children:
                if component.check(**kwargs) and self.columns[0].check(**kwargs):
                    content += component.render(**kwargs, object=row)
        content += "</div>"

        for column in self.columns[1:]:
            if not column.check(**kwargs):
                continue
            content += f"<div class='text-sm flex gap-2 truncate'>"
            content += (
                f"<span class='font-semibold text-primary'>{column.label}:</span>"
            )
            for component in column.children:
                if component.check(**kwargs):
                    content += f"<span>{component.render(**kwargs, object=row)}</span>"
            content += "</div>"

        return content

    def _render_empty(self) -> str:
        return f"""
            <div id='{self.uid}_empty' class="col-span-full text-center text-base-content/50 py-8">
                Table is empty
            </div>
        """

    def _render_single_select_card(self, row: Any, **kwargs) -> str:
        value = html.escape(self._get_value(row))
        display = html.escape(self._get_display_value(row))
        helper_name = f"TableSelection_{self.table_uid}".replace("-", "_")
        onclick = f"{helper_name}.selectSingle('{value}', '{display}')"

        return f"""
            <div id='{self.uid}_card_{row.pk}'
                 class='border border-base-content/10 rounded-box flex flex-col bg-base-100 p-2 cursor-pointer hover:bg-primary/10 transition-colors'
                 onclick="try {{ {onclick} }} catch(e) {{ console.error('Selection error:', e); }}">
                {self._render_card_content(row, **kwargs)}
            </div>
        """

    def _render_multi_select_card(self, row: Any, target_input: str, **kwargs) -> str:
        value = html.escape(self._get_value(row))
        display = html.escape(self._get_display_value(row))
        helper_name = f"TableSelection_{self.table_uid}".replace("-", "_")
        target_input_escaped = html.escape(target_input)

        return f"""
            <div id='{self.uid}_card_{row.pk}'
                 class='border border-base-content/10 rounded-box flex flex-col bg-base-100 p-2 relative'>
                <div class='absolute top-2 right-2'>
                    <input type="checkbox"
                           class="checkbox checkbox-primary checkbox-sm"
                           data-selection-value="{value}"
                           x-init="$el.checked = !!document.getElementById('{target_input_escaped}_item_{value}')"
                           onchange="try {{ {helper_name}.toggleItem('{value}', '{display}', this.checked) }} catch(e) {{ console.error('Toggle error:', e); }}" />
                </div>
                {self._render_card_content(row, **kwargs)}
            </div>
        """

    def _render_url_card(self, row: Any, **kwargs) -> str:
        url = self.get_url(row)
        return f"""
            <a href='{url}' hx-get='{url}' hx-target='#app-layout' hx-push-url='true'>
                <div id='{self.uid}_card_{row.pk}'
                     class='border border-base-content/10 rounded-box flex flex-col bg-base-100 p-2 cursor-pointer hover:bg-base-content/20'>
                    {self._render_card_content(row, **kwargs)}
                </div>
            </a>
        """

    def _render_static_card(self, row: Any, **kwargs) -> str:
        return f"""
            <div id='{self.uid}_card_{row.pk}'
                 class='border border-base-content/10 rounded-box flex flex-col bg-base-100 p-2'>
                {self._render_card_content(row, **kwargs)}
            </div>
        """

    def render_html(self, **kwargs) -> str:
        target_input = kwargs.get("target_input", "")

        data = self.get_value(**kwargs)
        objects = data.object_list if hasattr(data, "object_list") else data

        cards = ""
        if len(objects) == 0:
            cards = self._render_empty()
        elif self.select == "single":
            for row in objects:
                cards += self._render_single_select_card(row, **kwargs)
        elif self.select == "multi":
            for row in objects:
                cards += self._render_multi_select_card(row, target_input, **kwargs)
        elif self.url:
            for row in objects:
                cards += self._render_url_card(row, **kwargs)
        else:
            for row in objects:
                cards += self._render_static_card(row, **kwargs)

        return f"""
            <div id='{self.uid}' class="flex flex-col gap-4 @container">
                <div class="overflow-x-auto">
                    <div class="grid grid-cols-1 @md:grid-cols-2 @2xl:grid-cols-3 @3xl:grid-cols-4 gap-2">
                        {cards}
                    </div>
                </div>
            </div>
        """
