"""Tabs."""
import xml.etree.ElementTree as etree
from markdown.extensions import toc
from markdown.treeprocessors import Treeprocessor
from .block import Block, type_boolean
from ..blocks import BlocksExtension
import html

HEADERS = {'h1', 'h2', 'h3', 'h4', 'h5', 'h6'}


class TabbedTreeprocessor(Treeprocessor):
    """Tab tree processor."""

    def __init__(self, md, config):
        """Initialize."""

        super().__init__(md)

        self.alternate = config['alternate_style']
        self.slugify = config['slugify']
        self.combine_header_slug = config['combine_header_slug']
        self.sep = config["separator"]

    def get_parent_header_slug(self, root, header_map, parent_map, el):
        """Attempt retrieval of parent header slug."""

        parent = el
        last_parent = parent
        while parent is not root:
            last_parent = parent
            parent = parent_map[parent]
            if parent in header_map:
                headers = header_map[parent]
                header = None
                for i in list(parent):
                    if i is el and header is None:
                        break
                    if i is last_parent and header is not None:
                        return header.attrib.get("id", '')
                    if i in headers:
                        header = i
        return ''

    def run(self, doc):
        """Update tab IDs."""

        # Get a list of id attributes
        used_ids = set()
        parent_map = {}
        header_map = {}

        if self.combine_header_slug:
            parent_map = {c: p for p in doc.iter() for c in p}

        for el in doc.iter():
            if "id" in el.attrib:
                if self.combine_header_slug and el.tag in HEADERS:
                    parent = parent_map[el]
                    if parent in header_map:
                        header_map[parent].append(el)
                    else:
                        header_map[parent] = [el]
                used_ids.add(el.attrib["id"])

        for el in doc.iter():
            if isinstance(el.tag, str) and el.tag.lower() == 'div':
                classes = el.attrib.get('class', '').split()
                if 'tabbed-set' in classes and (not self.alternate or 'tabbed-alternate' in classes):
                    inputs = []
                    labels = []
                    if self.alternate:
                        for i in list(el):
                            if i.tag == 'input':
                                inputs.append(i)
                            if i.tag == 'div' and i.attrib.get('class', '') == 'tabbed-labels':
                                labels = [j for j in list(i) if j.tag == 'label']
                    else:
                        for i in list(el):
                            if i.tag == 'input':
                                inputs.append(i)
                            if i.tag == 'label':
                                labels.append(i)

                    # Generate slugged IDs
                    for inpt, label in zip(inputs, labels, strict=True):
                        innerhtml = toc.render_inner_html(toc.remove_fnrefs(label), self.md)
                        innertext = html.unescape(toc.strip_tags(innerhtml))
                        if self.combine_header_slug:
                            parent_slug = self.get_parent_header_slug(doc, header_map, parent_map, el)
                        else:
                            parent_slug = ''
                        slug = self.slugify(innertext, self.sep)
                        if parent_slug:
                            slug = parent_slug + self.sep + slug
                        slug = toc.unique(slug, used_ids)
                        inpt.attrib["id"] = slug
                        label.attrib["for"] = slug


class Tab(Block):
    """
    Tabbed container.

    Arguments (1 required):
    - A tab title.

    Options:
    - `new` (boolean): since consecutive tabs are automatically grouped, `new` can force a tab
      to start a new tab container.

    Content:
    Detail body.
    """

    NAME = 'tab'

    ARGUMENT = True
    OPTIONS = {
        'new': (False, type_boolean),
        'select': (False, type_boolean)
    }

    def on_init(self):
        """Handle initialization."""

        self.alternate_style = self.config['alternate_style']
        self.slugify = callable(self.config['slugify'])

        # Track tab group count across the entire page.
        if 'tab_group_count' not in self.tracker:
            self.tracker['tab_group_count'] = 0

        self.tab_content = None

    def last_child(self, parent):
        """Return the last child of an `etree` element."""

        if len(parent):
            return parent[-1]
        else:
            return None

    def on_add(self, block):
        """Adjust where the content is added."""

        if self.tab_content is None:
            if self.alternate_style:
                for d in block.findall('div'):
                    c = d.attrib['class']
                    if c == 'tabbed-content' or c.startswith('tabbed-content '):
                        self.tab_content = list(d)[-1]
                        break
            else:
                self.tab_content = list(block)[-1]

        return self.tab_content

    def on_create(self, parent):
        """Create the element."""

        new_group = self.options['new']
        select = self.options['select']
        title = self.argument
        sibling = self.last_child(parent)
        tabbed_set = 'tabbed-set' if not self.alternate_style else 'tabbed-set tabbed-alternate'
        index = 0
        labels = None
        content = None

        if (
            sibling is not None and sibling.tag.lower() == 'div' and
            sibling.attrib.get('class', '') == tabbed_set and
            not new_group
        ):
            first = False
            tab_group = sibling

            if self.alternate_style:
                index = [index for index, _ in enumerate(tab_group.findall('input'), 1)][-1]
                for d in tab_group.findall('div'):
                    if d.attrib['class'] == 'tabbed-labels':
                        labels = d
                    elif d.attrib['class'] == 'tabbed-content':
                        content = d
                    if labels is not None and content is not None:
                        break
        else:
            first = True
            self.tracker['tab_group_count'] += 1
            tab_group = etree.SubElement(
                parent,
                'div',
                {'class': tabbed_set, 'data-tabs': '%d:0' % self.tracker['tab_group_count']}
            )

            if self.alternate_style:
                labels = etree.SubElement(
                    tab_group,
                    'div',
                    {'class': 'tabbed-labels'}
                )
                content = etree.SubElement(
                    tab_group,
                    'div',
                    {'class': 'tabbed-content'}
                )

        data = tab_group.attrib['data-tabs'].split(':')
        tab_set = int(data[0])
        tab_count = int(data[1]) + 1

        attributes = {
            "name": "__tabbed_%d" % tab_set,
            "type": "radio"
        }

        if not self.slugify:
            attributes['id'] = "__tabbed_%d_%d" % (tab_set, tab_count)

        attributes2 = {"for": "__tabbed_%d_%d" % (tab_set, tab_count)} if not self.slugify else {}

        if first or select:
            attributes['checked'] = 'checked'
            # Remove any previously assigned "checked states" to siblings
            for i in tab_group.findall('input'):
                if i.attrib.get('name', '') == f'__tabbed_{tab_set}':
                    if 'checked' in i.attrib:
                        del i.attrib['checked']

        if self.alternate_style:
            input_el = etree.Element(
                'input',
                attributes
            )
            tab_group.insert(index, input_el)
            lab = etree.SubElement(
                labels,
                "label",
                attributes2
            )
            lab.text = title

            attrib = {'class': 'tabbed-block'}
            etree.SubElement(
                content,
                "div",
                attrib
            )
        else:
            etree.SubElement(
                tab_group,
                'input',
                attributes
            )
            lab = etree.SubElement(
                tab_group,
                "label",
                attributes2
            )
            lab.text = title

            etree.SubElement(
                tab_group,
                "div",
                {
                    "class": "tabbed-content"
                }
            )

        tab_group.attrib['data-tabs'] = '%d:%d' % (tab_set, tab_count)

        return tab_group


class TabExtension(BlocksExtension):
    """Admonition Blocks Extension."""

    def __init__(self, *args, **kwargs):
        """Initialize."""

        self.config = {
            'alternate_style': [False, "Use alternate style - Default: False"],
            'slugify': [0, "Slugify function used to create tab specific IDs - Default: None"],
            'combine_header_slug': [False, "Combine the tab slug with the slug of the parent header - Default: False"],
            'separator': ['-', "Slug separator - Default: '-'"]
        }

        super().__init__(*args, **kwargs)

    def extendMarkdownBlocks(self, md, block_mgr):
        """Extend Markdown blocks."""

        block_mgr.register(Tab, self.getConfigs())
        if callable(self.getConfig('slugify')):
            slugs = TabbedTreeprocessor(md, self.getConfigs())
            md.treeprocessors.register(slugs, 'tab_slugs', 4)


def makeExtension(*args, **kwargs):
    """Return extension."""

    return TabExtension(*args, **kwargs)
