"""Main entrypoint."""

import logging
from dataclasses import dataclass

import click
import pydantic
import yaml
from i3ipc import Connection

from sway_out.layout_creation import create_layout_from_workspace

from .applications import launch_applications_from_layout, match_existing_windows
from .connection import find_con_by_id, run_command, run_command_on
from .layout import (
    check_layout,
    create_layout,
    dissolve_layout,
    find_leftover_windows,
    resize_layout,
)
from .layout_files import (
    find_focused_element_in_layout,
    load_layout_configuration,
    map_workspaces,
    save_layout_configuration,
)
from .marks import apply_marks
from .matching import find_current_workspace
from .notifications import error_notification, progress_notification
from .utils import PROG_NAME, get_con_description

logger = logging.getLogger(__name__)


@dataclass
class GlobalState:
    """The user-provided configuration for the application and some global state."""

    connection: Connection
    notifications: bool = True


@click.group()
@click.version_option(prog_name=PROG_NAME, package_name="sway-out")
@click.option(
    "--notifications/--no-notifications",
    default=True,
    help="Enable or disable notifications.",
)
@click.pass_context
def main(ctx: click.Context, notifications: bool):
    """Main entrypoint."""

    logging.basicConfig(level=logging.DEBUG)
    ctx.obj = GlobalState(Connection(), notifications)


@main.command("apply")
@click.argument("layout_file", type=click.File("r"))
@click.pass_context
def main_apply(ctx: click.Context, layout_file):
    connection: Connection = ctx.obj.connection
    assert connection is not None
    try:
        configuration = load_layout_configuration(layout_file)
    except (yaml.YAMLError, pydantic.ValidationError) as e:
        if ctx.obj.notifications:
            error_notification("Error during layout creation", str(e))

        click.echo(f"Failed to read layout configuration: {e}", err=True)
        return

    workspace_layout_mapping = map_workspaces(connection, configuration)

    with progress_notification("Applying layout", "Workspace") as notification:
        if ctx.obj.notifications:
            notification.start()
        for index, (workspace_name, workspace_layout) in enumerate(
            workspace_layout_mapping.items()
        ):
            notification.update(index + 1, len(workspace_layout_mapping))
            run_command(connection, f"workspace {workspace_name}")
            workspace_con = find_current_workspace(connection)
            workspace_layout._con_id = workspace_con.id
            assert workspace_con is not None, "No current workspace found?"
            logging.debug("Matching existing windows on workspace: %s", workspace_name)
            match_existing_windows(workspace_con, workspace_layout)
            logging.debug("Dissolving layout for workspace: %s", workspace_name)
            dissolve_layout(connection, workspace_con)
            logger.info("Applying layout for workspace: %s", workspace_name)
            launch_applications_from_layout(connection, workspace_layout)
            create_layout(connection, workspace_layout)
            leftover_windows = find_leftover_windows(connection, workspace_layout)
            if not leftover_windows:
                resize_layout(connection, workspace_layout)
            else:
                error_message = (
                    f"Found leftover windows on workspace {workspace_name}:\n"
                    + "\n".join(f"- {get_con_description(w)}" for w in leftover_windows)
                    + "\n"
                    + "Not resizing layout."
                )
                click.echo(error_message, err=True)
                if ctx.obj.notifications:
                    error_notification("Applying layout", error_message)
                notification.successful = False
            apply_marks(connection, workspace_layout)
            if not leftover_windows and not check_layout(connection, workspace_layout):
                error_message = f"Failed to apply layout to {workspace_name}"
                click.echo(error_message, err=True)
                if ctx.obj.notifications:
                    error_notification("Applying layout", error_message)
                notification.successful = False

        focused_layout = find_focused_element_in_layout(configuration)
        if focused_layout is not None:
            focused_con = find_con_by_id(connection, focused_layout._con_id)
            run_command_on(focused_con, "focus")
            logger.info(
                f"Focused element in layout: {get_con_description(focused_con)}",
            )
        else:
            logger.debug("No focused element found in layout")

        logger.info(f"Applied layout for {len(workspace_layout_mapping)} workspace(s)")


@main.command("save")
@click.argument("layout_file", type=click.File("w"), required=False)
@click.option(
    "-w",
    "--workspace",
    type=str,
    required=False,
    multiple=True,
    help="Restrict to specific workspaces",
)
@click.pass_context
def main_save(ctx: click.Context, layout_file, workspace):
    connection: Connection = ctx.obj.connection
    assert connection is not None
    workspace_names = set(workspace) if workspace else None

    with progress_notification("Creating layout", "Creation") as notification:
        if ctx.obj.notifications:
            notification.start()
        layout = create_layout_from_workspace(connection, list(workspace) or None)
        save_layout_configuration(layout, layout_file)

    logger.info("Layout creation completed.")


if __name__ == "__main__":
    main()
