# SPDX-FileCopyrightText: © 2023 Christian BUHTZ <c.buhtz@posteo.jp>
# SPDX-FileCopyrightText: © 2025 Michael Olie <aolie1794@gmail.com>
#
# SPDX-License-Identifier: GPL-3.0-only
#
# This file is part of the program "Hyperorg" which is released under
# GNU General Public License v3 (GPLv3). See LICENSES directory
# or go to <https://spdx.org/licenses/GPL-3.0-only.html>.
# pylint: disable=too-many-lines
"""Tests about tokens and creating runs based on tokens."""
import unittest
from hyperorg import textruns


class TokenPositions(unittest.TestCase):
    """Test `textruns._token_position()`"""

    # pylint: disable=missing-function-docstring,protected-access
    def test_token_close(self):
        self.assertEqual(
            textruns._token_positions('Foo* bar'),
            [(3, textruns.Token.IMPORTANT, textruns.TokenState.CLOSE)]
        )

    def test_token_open(self):
        self.assertEqual(
            textruns._token_positions('Foo *bar'),
            [(4, textruns.Token.IMPORTANT, textruns.TokenState.OPEN)]
        )

    def test_all_tokens(self):
        """All kinds of tokens."""
        for token in textruns.Token:
            if token == textruns.Token.LINK:
                text = 'Foo [[marku]] bar'
                expect = [
                    (4, token, textruns.TokenState.OPEN),
                    (11, token, textruns.TokenState.CLOSE)
                ]

            # elif token == textruns.Token.MATH_DOLLAR:
            #     text = 'Foo $marku$ bar'
            #     expect = [
            #         (4, token, textruns.TokenState.OPEN),
            #         (10, token, textruns.TokenState.CLOSE)
            #     ]
            elif token == textruns.Token.MATH_PARENS:
                text = r'Foo \(marku\) bar'
                expect = [
                    (4, token, textruns.TokenState.OPEN),
                    (11, token, textruns.TokenState.CLOSE)
                ]
            # elif token == textruns.Token.MATH_DBL_DOLLAR:
            #     text = r'Foo $$marku$$ bar'
            #     expect = [
            #         (4, token, textruns.TokenState.OPEN),
            #         (12, token, textruns.TokenState.CLOSE)
            #     ]
            # elif token == textruns.Token.MATH_BRACKETS:
            #     text = r'Foo \[marku\] bar'
            #     expect = [
            #         (4, token, textruns.TokenState.OPEN),
            #         (11, token, textruns.TokenState.CLOSE)
            #     ]

            else:
                text = f'Foo {token.value}marku{token.value} bar'
                expect = [
                    (4, token, textruns.TokenState.OPEN),
                    (10, token, textruns.TokenState.CLOSE)
                ]

            result = textruns._token_positions(text)
            self.assertEqual(result, expect)

    def test_side_by_side(self):
        """One markup after the other."""
        text = 'Foo ~code~ and /italic/ bar'
        self.assertEqual(
            textruns._token_positions(text),
            [
                (4, textruns.Token.CODE, textruns.TokenState.OPEN),
                (9, textruns.Token.CODE, textruns.TokenState.CLOSE),
                (15, textruns.Token.EMPHASIZED, textruns.TokenState.OPEN),
                (22, textruns.Token.EMPHASIZED, textruns.TokenState.CLOSE),
            ]
        )

    def test_entangled(self):
        """Entangled markup tokens.

        It doesn't matter at this point if this are correct pairs or not.
        """

        text = '/Worf *Picard/ Diana*'
        self.assertEqual(
            textruns._token_positions(text),
            [
                (0, textruns.Token.EMPHASIZED, textruns.TokenState.OPEN),
                (6, textruns.Token.IMPORTANT, textruns.TokenState.OPEN),
                (13, textruns.Token.EMPHASIZED, textruns.TokenState.CLOSE),
                (20, textruns.Token.IMPORTANT, textruns.TokenState.CLOSE),
            ]
        )

    def test_enclosed(self):
        """Nested markup tokens."""
        text = 'Frank /Burns *is* a/ fret.'
        self.assertEqual(
            textruns._token_positions(text),
            [
                (6, textruns.Token.EMPHASIZED, textruns.TokenState.OPEN),
                (13, textruns.Token.IMPORTANT, textruns.TokenState.OPEN),
                (16, textruns.Token.IMPORTANT, textruns.TokenState.CLOSE),
                (19, textruns.Token.EMPHASIZED, textruns.TokenState.CLOSE),
            ]
        )

    def test_link(self):
        """Link tokens."""
        text = 'Giants [[id:1234][Foo]] Cyberspace'
        result = textruns._token_positions(text)

        self.assertEqual(
            result,
            [
                (7, textruns.Token.LINK, textruns.TokenState.OPEN),
                (21, textruns.Token.LINK, textruns.TokenState.CLOSE)
            ]
        )

    def test_none_token(self):
        """
        Blanks after OPEN or before CLOSE is not allowed. It is not a token.
        """
        self.assertEqual(
            textruns._token_positions(" / foobar /"),
            []
        )


class TokenPairs(unittest.TestCase):
    """Test `textruns._token_pairs()`"""

    # pylint: disable=missing-function-docstring,protected-access
    def test_all_tokens(self):
        for token in textruns.Token:
            positions = [
                (6, token, textruns.TokenState.OPEN),
                (20, token, textruns.TokenState.CLOSE)]
            expect = [(token, 6, 20)]

        self.assertEqual(textruns._token_pairs(positions), expect)

    def test_side_by_side(self):
        positions = [
            (2, textruns.Token.EMPHASIZED, textruns.TokenState.OPEN),
            (6, textruns.Token.EMPHASIZED, textruns.TokenState.CLOSE),
            (9, textruns.Token.IMPORTANT, textruns.TokenState.OPEN),
            (14, textruns.Token.IMPORTANT, textruns.TokenState.CLOSE)
        ]

        self.assertEqual(
            textruns._token_pairs(positions),
            [
                (textruns.Token.EMPHASIZED, 2, 6),
                (textruns.Token.IMPORTANT, 9, 14),
            ]
        )

    def test_enclosed(self):
        """The encloused markup is deleted.

        The intention is to parse the inner content of the markup in another
        iteration.
        """

        # The two inner tokens are ignored
        positions = [
            (2, textruns.Token.EMPHASIZED, textruns.TokenState.OPEN),
            (9, textruns.Token.IMPORTANT, textruns.TokenState.OPEN),
            (14, textruns.Token.IMPORTANT, textruns.TokenState.CLOSE),
            (20, textruns.Token.EMPHASIZED, textruns.TokenState.CLOSE),
        ]

        self.assertEqual(
            textruns._token_pairs(positions),
            [
                (textruns.Token.EMPHASIZED, 2, 20),
            ]
        )

    def test_entangled(self):
        """First wins.

        The pairs are entangled like 'a /b *c/ d* e'. The first wins means
        that the emphasized is closed first and valid in that case.
        """
        positions = [
            (0, textruns.Token.EMPHASIZED, textruns.TokenState.OPEN),
            (6, textruns.Token.IMPORTANT, textruns.TokenState.OPEN),
            (13, textruns.Token.EMPHASIZED, textruns.TokenState.CLOSE),
            (20, textruns.Token.IMPORTANT, textruns.TokenState.CLOSE)
        ]

        self.assertEqual(
            textruns._token_pairs(positions),
            [(textruns.Token.EMPHASIZED, 0, 13)]
        )

    def test_link(self):
        positions = [
            (7, textruns.Token.LINK, textruns.TokenState.OPEN),
            (21, textruns.Token.LINK, textruns.TokenState.CLOSE)
        ]

        self.assertEqual(
            textruns._token_pairs(positions),
            [(textruns.Token.LINK, 7, 21)]
        )


class RunsFromTokenPairs(unittest.TestCase):
    """Test `textruns._runs_from_token_pairs()`"""

    # pylint: disable=missing-function-docstring,protected-access
    def test_markup_tokens(self):
        """
        Testing all markup token types (excluding links and math).
        This test checks that all markup token types are handled
        correctly by `_runs_from_token_pairs`.
        The test uses a generic marker ('X') in the string, as the marker
        itself does not affect the parsing—only its position does.
        """

        for token in textruns.Token:
            # Skip non-markup tokens
            non_markup_tokens = (
                textruns.Token.LINK,
                textruns.Token.MATH_DOLLAR,
                textruns.Token.MATH_PARENS,
                # textruns.Token.MATH_DBL_DOLLAR,
                # textruns.Token.MATH_BRACKETS
            )
            if token in non_markup_tokens:
                continue

            text = 'Riker XwithX Data'
            pairs = [(token, 6, 11)]
            result = textruns._runs_from_token_pairs(text, pairs)

            self.assertEqual(str(result[0]), 'Riker ')
            self.assertEqual(str(result[1].runs[0]), 'with')
            self.assertEqual(result[1].markup_type.value, token.value)
            self.assertEqual(result[2], ' Data')

    def test_link(self):
        text = 'Riker [[id:with]] Data'
        pairs = [(textruns.Token.LINK, 6, 15)]

        result = textruns._runs_from_token_pairs(text, pairs)

        self.assertEqual(result[0], 'Riker ')
        self.assertEqual(len(result[1]), 0)
        self.assertEqual(result[1].target, 'with')
        self.assertEqual(result[1].link_type, textruns.Link.Type.ORGID)
        self.assertEqual(result[2], ' Data')

    def test_inlinemath_dollars(self):
        """Test MATH_DOLLAR token handling."""
        text = 'Riker $with$ Data'
        pairs = [(textruns.Token.MATH_DOLLAR, 6, 11)]
        result = textruns._runs_from_token_pairs(text, pairs)

        self.assertEqual(str(result[0]), 'Riker ')
        self.assertIsInstance(result[1], textruns.InlineMath)
        self.assertEqual(str(result[1].runs[0]), 'with')
        self.assertEqual(result[2], ' Data')

    def test_inlinemath_parens(self):
        """Test MATH_PARENS token handling."""
        text = r'Riker \(with\) Data'
        pairs = [(textruns.Token.MATH_PARENS, 6, 12)]
        result = textruns._runs_from_token_pairs(text, pairs)

        self.assertEqual(str(result[0]), 'Riker ')
        self.assertIsInstance(result[1], textruns.InlineMath)
        self.assertEqual(str(result[1].runs[0]), 'with')
        self.assertEqual(result[2], ' Data')

    def test_inlinemath_dollar_with_complex_content(self):
        """Test MATH_DOLLAR token with complex content."""
        text = 'The equation $x^2 + y^2 = z^2$ is famous'
        pairs = [(textruns.Token.MATH_DOLLAR, 13, 29)]
        result = textruns._runs_from_token_pairs(text, pairs)

        self.assertEqual(result[0], 'The equation ')

        self.assertIsInstance(result[1], textruns.InlineMath)
        self.assertEqual(str(result[1][0]), 'x^2 + y^2 = z^2')

        self.assertEqual(result[2], ' is famous')

    def test_inlinemath_and_markup_tokens(self):
        """Test mixing math tokens with markup tokens"""
        text = (
            r'The *important* equation $E=mc^2$ and \(F=ma\) are fundamental'
        )
        pairs = [
            (textruns.Token.IMPORTANT, 4, 14),
            (textruns.Token.MATH_DOLLAR, 25, 32),
            (textruns.Token.MATH_PARENS, 38, 44),
        ]
        result = textruns._runs_from_token_pairs(text, pairs)

        self.assertEqual(str(result[0]), 'The ')

        # Important markup
        self.assertIsInstance(result[1], textruns.Markup)
        self.assertEqual(result[1][0], 'important')
        self.assertEqual(result[1].markup_type, textruns.Markup.Type.IMPORTANT)

        self.assertEqual(result[2], ' equation ')

        # Inline (Dollar) math
        self.assertIsInstance(result[3], textruns.InlineMath)
        self.assertEqual(str(result[3][0]), 'E=mc^2')

        self.assertEqual(result[4], ' and ')

        # Inline (Parens) math
        self.assertIsInstance(result[5], textruns.InlineMath)
        self.assertEqual(str(result[5][0]), 'F=ma')

        self.assertEqual(result[6], ' are fundamental')

    def test_side_by_side(self):
        text = 'Foo ~code~ and /italic/ bar'
        pairs = [
            (textruns.Token.CODE, 4, 9),
            (textruns.Token.EMPHASIZED, 15, 22),
        ]
        result = textruns._runs_from_token_pairs(text, pairs)

        # text runs
        self.assertEqual(str(result[0]), 'Foo ')
        self.assertEqual(str(result[2]), ' and ')
        self.assertEqual(str(result[4]), ' bar')

        # markups
        self.assertEqual(str(result[1].runs[0]), 'code')
        self.assertEqual(str(result[3].runs[0]), 'italic')
        self.assertEqual(
            result[1].markup_type, textruns.Markup.Type.CODE)
        self.assertEqual(
            result[3].markup_type, textruns.Markup.Type.EMPHASIZED)
