from dataclasses import dataclass
from typing import Any, Optional, Union

from talon import Module, actions

from .targets.target_types import (
    CursorlessDestination,
    CursorlessTarget,
    ImplicitDestination,
)


@dataclass
class InsertionSnippet:
    name: str
    destination: CursorlessDestination


@dataclass
class CommunityInsertionSnippet:
    body: str
    scopes: list[str] | None = None


@dataclass
class CommunityWrapperSnippet:
    body: str
    variable_name: str
    scope: str | None = None


mod = Module()

mod.list("cursorless_insert_snippet_action", desc="Cursorless insert snippet action")

# Deprecated tag; we should probably remove this and notify users that they
# should get rid of it, but I don't think it's worth the effort right now
mod.tag(
    "cursorless_experimental_snippets",
    desc="tag for enabling experimental snippet support",
)

mod.tag(
    "cursorless_use_community_snippets",
    "If active use community snippets instead of Cursorless snippets",
)

mod.list("cursorless_wrapper_snippet", desc="Cursorless wrapper snippet")
mod.list(
    "cursorless_insertion_snippet_no_phrase",
    desc="Cursorless insertion snippets that don't accept a phrase",
)
mod.list(
    "cursorless_insertion_snippet_single_phrase",
    desc="Cursorless insertion snippet that can accept a single phrase",
)
mod.list("cursorless_phrase_terminator", "Contains term used to terminate a phrase")


@mod.capture(
    rule="({user.cursorless_insertion_snippet_no_phrase} | {user.cursorless_insertion_snippet_single_phrase}) [<user.cursorless_destination>]"
)
def cursorless_insertion_snippet(m) -> InsertionSnippet:
    try:
        name = m.cursorless_insertion_snippet_no_phrase
    except AttributeError:
        name = m.cursorless_insertion_snippet_single_phrase.split(".")[0]

    try:
        destination = m.cursorless_destination
    except AttributeError:
        destination = ImplicitDestination()

    return InsertionSnippet(name, destination)


def wrap_with_snippet(snippet_description: dict, target: CursorlessTarget):
    actions.user.private_cursorless_command_and_wait(
        {
            "name": "wrapWithSnippet",
            "snippetDescription": snippet_description,
            "target": target,
        },
    )


def insert_snippet(snippet_description: dict, destination: CursorlessDestination):
    actions.user.private_cursorless_command_and_wait(
        {
            "name": "insertSnippet",
            "snippetDescription": snippet_description,
            "destination": destination,
        },
    )


def insert_named_snippet(
    name: str,
    destination: CursorlessDestination,
    substitutions: Optional[dict] = None,
):
    snippet: dict = {
        "type": "named",
        "name": name,
    }
    if substitutions is not None:
        snippet["substitutions"] = substitutions
    insert_snippet(snippet, destination)


def insert_custom_snippet(
    body: str,
    destination: CursorlessDestination,
    scope_types: Optional[list[dict]] = None,
):
    snippet: dict = {
        "type": "custom",
        "body": body,
    }

    if scope_types:
        snippet["scopeTypes"] = scope_types

    insert_snippet(snippet, destination)


@mod.action_class
class Actions:
    def private_cursorless_insert_snippet(insertion_snippet: InsertionSnippet):  # pyright: ignore [reportGeneralTypeIssues]
        """Execute Cursorless insert snippet action"""
        insert_named_snippet(
            insertion_snippet.name,
            insertion_snippet.destination,
        )

    def private_cursorless_insert_snippet_with_phrase(
        snippet_description: str,  # pyright: ignore [reportGeneralTypeIssues]
        text: str,
    ):
        """Cursorless: Insert snippet <snippet_description> with phrase <text>"""
        snippet_name, snippet_variable = snippet_description.split(".")
        insert_named_snippet(
            snippet_name,
            ImplicitDestination(),
            {snippet_variable: text},
        )

    def cursorless_insert_snippet_by_name(name: str):  # pyright: ignore [reportGeneralTypeIssues]
        """Cursorless: Insert named snippet <name>"""
        insert_named_snippet(
            name,
            ImplicitDestination(),
        )

    def cursorless_insert_snippet(
        body: str,  # pyright: ignore [reportGeneralTypeIssues]
        destination: CursorlessDestination = ImplicitDestination(),
        scope_type: Optional[Union[str, list[str]]] = None,
    ):
        """Cursorless: Insert custom snippet <body>"""
        if isinstance(scope_type, str):
            scope_type = [scope_type]

        if scope_type is not None:
            scope_types = [{"type": st} for st in scope_type]
        else:
            scope_types = None

        insert_custom_snippet(body, destination, scope_types)

    def cursorless_wrap_with_snippet_by_name(
        name: str,  # pyright: ignore [reportGeneralTypeIssues]
        variable_name: str,
        target: CursorlessTarget,
    ):
        """Cursorless: Wrap target with a named snippet <name>"""
        wrap_with_snippet(
            {
                "type": "named",
                "name": name,
                "variableName": variable_name,
            },
            target,
        )

    def cursorless_wrap_with_snippet(
        body: str,  # pyright: ignore [reportGeneralTypeIssues]
        target: CursorlessTarget,
        variable_name: Optional[str] = None,
        scope: Optional[str] = None,
    ):
        """Cursorless: Wrap target with custom snippet <body>"""
        snippet_arg: dict[str, Any] = {
            "type": "custom",
            "body": body,
        }
        if scope is not None:
            snippet_arg["scopeType"] = {"type": scope}
        if variable_name is not None:
            snippet_arg["variableName"] = variable_name
        wrap_with_snippet(
            snippet_arg,
            target,
        )

    def private_cursorless_insert_community_snippet(
        name: str,  # pyright: ignore [reportGeneralTypeIssues]
        destination: CursorlessDestination,
    ):
        """Cursorless: Insert community snippet <name>"""
        snippet: CommunityInsertionSnippet = actions.user.get_insertion_snippet(name)
        actions.user.cursorless_insert_snippet(
            snippet.body, destination, snippet.scopes
        )

    def private_cursorless_wrap_with_community_snippet(
        name: str,  # pyright: ignore [reportGeneralTypeIssues]
        target: CursorlessTarget,
    ):
        """Cursorless: Wrap target with community snippet <name>"""
        snippet: CommunityWrapperSnippet = actions.user.get_wrapper_snippet(name)
        actions.user.cursorless_wrap_with_snippet(
            snippet.body, target, snippet.variable_name, snippet.scope
        )
