"""Closed Declarations route catalogue and injected root factory."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import Final, get_args, override
from textual.app import ComposeResult
from textual.screen import Screen
from textual.widgets import DataTable, Static
from ....application.modelo.declarations_calendar import (
DeclarationsCalendarProjectionV1,
DeclarationsCalendarSource,
)
from ....application.modelo.declarations_workspace import (
DeclarationsWorkspaceAvailability,
DeclarationsWorkspaceProjectionV1,
DeclarationsWorkspaceZone,
)
from ....application.operator_actions.models import ActionReference
from ....application.overview.home import HomeAvailability
from ..components.widgets import ContentDataTable, ContentScroll
from ..navigation import TuiScreenContextV1, TuiScreenFactoryV1
from .action_guards import require_canonical_declarations_actions
from .calendar import DeclarationsCalendarScreen
from .controller import (
DeclarationsCalendarController,
DeclarationsWorkspaceController,
DeclarationsWorkspaceScreen,
declarations_copy,
)
from .filing_history import DeclarationsFilingHistoryScreen
from .models import (
CalendarEntryHandoffV1,
CalendarRecoveryHandoffV1,
DeclarationsDestinationIdV1,
DeclarationsRouteTargetV1,
FilingHandoffV1,
ModeloWorkspaceScreenFactoryV1,
RevisionHandoffV1,
)
from .overview import DeclarationsModeloWorkspaceLauncherScreen, DeclarationsOverviewScreen
from .revisions import DeclarationsRevisionsScreen
type DeclarationsInternalScreenFactoryV1 = Callable[[DeclarationsWorkspaceController], DeclarationsWorkspaceScreen]
[docs]
class DeclarationsUnavailableScreen(DeclarationsWorkspaceScreen):
"""Explicit refusal without fabricating an empty result."""
def __init__(self, controller: DeclarationsWorkspaceController, target: DeclarationsRouteTargetV1) -> None:
"""Retain the refused typed target."""
super().__init__(controller, id="declarations-unavailable-screen")
self.target = target
[docs]
@override
def compose(self) -> ComposeResult:
yield Static(declarations_copy("tui.declarations.unavailable.title"), classes="cadrumo-banner", markup=False)
with ContentScroll(id="declarations-page", classes="cadrumo-scroll declarations-page"):
yield ContentDataTable[str](id="declarations-navigation", cursor_type="row", zebra_stripes=True)
yield Static(
declarations_copy("tui.declarations.refusal.source"),
id="declarations-refusal",
classes="declarations-refusal",
markup=False,
)
[docs]
def on_mount(self) -> None:
"""Populate navigation while preserving the visible refusal."""
self.populate_navigation()
self.query_one("#declarations-navigation", DataTable).focus()
[docs]
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
"""Permit navigation away from the unavailable body."""
self.handle_navigation(event)
[docs]
@dataclass(frozen=True, slots=True)
class DeclarationsRouteV1:
"""One route and its projection zone."""
destination: DeclarationsDestinationIdV1
zone: DeclarationsWorkspaceZone | None
factory: DeclarationsInternalScreenFactoryV1 | None
DECLARATIONS_ROUTES: Final = (
DeclarationsRouteV1("declarations.overview", DeclarationsWorkspaceZone.DECLARATIONS, DeclarationsOverviewScreen),
DeclarationsRouteV1(
"declarations.revisions", DeclarationsWorkspaceZone.CALCULATION_REVISIONS, DeclarationsRevisionsScreen
),
DeclarationsRouteV1(
"declarations.filing_history", DeclarationsWorkspaceZone.FILING_HISTORY, DeclarationsFilingHistoryScreen
),
DeclarationsRouteV1("declarations.calendar", None, None),
DeclarationsRouteV1("declarations.modelo_workspace", DeclarationsWorkspaceZone.DECLARATIONS, None),
)
_ROUTES_BY_ID: Final = {route.destination: route for route in DECLARATIONS_ROUTES}
[docs]
def declared_declarations_destination_ids() -> frozenset[str]:
"""Return the destination identities from the defining literal."""
return frozenset(item for item in get_args(DeclarationsDestinationIdV1.__value__) if isinstance(item, str))
if frozenset(_ROUTES_BY_ID) != declared_declarations_destination_ids() or len(_ROUTES_BY_ID) != len(
DECLARATIONS_ROUTES
):
raise ValueError("Declarations routes must cover the closed catalogue exactly once")
def _calendar_is_observable(calendar: DeclarationsCalendarProjectionV1) -> bool:
"""Return whether the authoritative schedule source can be displayed."""
return next(
item.availability for item in calendar.sources if item.source is DeclarationsCalendarSource.SCHEDULE
) in {HomeAvailability.AVAILABLE, HomeAvailability.STALE}
def _workspace_route_is_available(
controller: DeclarationsWorkspaceController,
route: DeclarationsRouteV1,
zone: DeclarationsWorkspaceZone,
) -> bool:
"""Require both an observable zone and a concrete screen admission."""
state = controller.zone_state(zone)
observable = state.availability in {
DeclarationsWorkspaceAvailability.AVAILABLE,
DeclarationsWorkspaceAvailability.STALE,
}
return observable and (route.factory is not None or controller.modelo_workspace_factory is not None)
def _resolve_calendar_screen(
controller: DeclarationsWorkspaceController,
target: DeclarationsRouteTargetV1,
) -> Screen[None]:
"""Resolve the calendar only after its schedule source is observable."""
calendar = controller.calendar_projection
if calendar is None or not _calendar_is_observable(calendar):
return DeclarationsUnavailableScreen(controller, target)
return DeclarationsCalendarScreen(
DeclarationsCalendarController(
controller.context,
calendar,
entry_handoff=controller.calendar_entry_handoff,
recovery_handoff=controller.calendar_recovery_handoff,
)
)
[docs]
def resolve_declarations_screen(
controller: DeclarationsWorkspaceController,
target: DeclarationsRouteTargetV1,
) -> Screen[None]:
"""Resolve a target without I/O or controller construction."""
route = _ROUTES_BY_ID[target.destination]
if target.zone is not route.zone:
raise ValueError("Declarations route target and zone disagree")
if route.destination == "declarations.calendar":
return _resolve_calendar_screen(controller, target)
if route.zone is None:
raise ValueError("Declarations non-calendar route requires a workspace zone")
if not _workspace_route_is_available(controller, route, route.zone):
return DeclarationsUnavailableScreen(controller, target)
if route.factory is None:
return DeclarationsModeloWorkspaceLauncherScreen(controller)
return route.factory(controller)
[docs]
def declarations_screen_factory(
projection: DeclarationsWorkspaceProjectionV1,
*,
work_action: ActionReference,
revisions_action: ActionReference,
filing_action: ActionReference,
modelo_workspace_factory: ModeloWorkspaceScreenFactoryV1 | None = None,
revision_handoff: RevisionHandoffV1 | None = None,
filing_handoff: FilingHandoffV1 | None = None,
calendar_projection: DeclarationsCalendarProjectionV1 | None = None,
calendar_entry_handoff: CalendarEntryHandoffV1 | None = None,
calendar_recovery_handoff: CalendarRecoveryHandoffV1 | None = None,
) -> TuiScreenFactoryV1:
"""Bind only injected facts, admissions, and typed handoffs."""
require_canonical_declarations_actions(
work_action=work_action,
revisions_action=revisions_action,
filing_action=filing_action,
)
def create(context: TuiScreenContextV1) -> Screen[None]:
controller = DeclarationsWorkspaceController(
context,
projection,
work_action=work_action,
revisions_action=revisions_action,
filing_action=filing_action,
modelo_workspace_factory=modelo_workspace_factory,
revision_handoff=revision_handoff,
filing_handoff=filing_handoff,
calendar_projection=calendar_projection,
calendar_entry_handoff=calendar_entry_handoff,
calendar_recovery_handoff=calendar_recovery_handoff,
)
# A caller that asks for a calendar row -- Home's agenda -- opens the
# calendar itself; everything else opens on the overview, which
# restores a declaration row from the same focus.
focus = context.focus
opening = (
"declarations.calendar"
if focus is not None and focus.semantic_key.startswith("declarations.calendar")
else "declarations.overview"
)
return resolve_declarations_screen(controller, controller.target(opening))
return create
__all__ = [
"DECLARATIONS_ROUTES",
"DeclarationsRouteV1",
"DeclarationsUnavailableScreen",
"declarations_screen_factory",
"resolve_declarations_screen",
]