// SPDX-FileCopyrightText: 2026 FocusFlow contributors
// SPDX-License-Identifier: AGPL-3.0-only

/// Composes the FocusFlow app around persistent SQLite scheduler state.
library;

import 'dart:io';

import 'package:scheduler_core/scheduler_core.dart' hide logger;
import 'package:scheduler_core/scheduler_core.dart'
    as scheduler_core
    show logger;
import 'package:scheduler_persistence_sqlite/sqlite.dart';

import '../controllers/backlog_board_controller.dart';
import '../controllers/scheduler_command_controller.dart';
import '../controllers/selected_date_controller.dart';
import '../controllers/today_screen_controller.dart';
import 'runtime/focus_flow_file_logger.dart';
import 'runtime/focus_flow_runtime_config.dart';
import 'demo_scheduler_composition.dart' show formatSchedulerDateLabel;
import 'scheduler_app_composition.dart';

/// Persistent SQLite-backed scheduler composition for normal app runtime.
final class PersistentSchedulerComposition implements SchedulerAppComposition {
  /// Creates a persistent composition from already-open collaborators.
  PersistentSchedulerComposition._({
    required this.runtime,
    required this.todayQuery,
    required this.managementUseCases,
    required this.commandUseCases,
    required this.date,
    required this.readAt,
    required this.logger,
    required this.uiTimeZone,
    required this.clock,
    required this.timeZoneResolver,
  });

  /// Opens the configured SQLite runtime and bootstraps required owner state.
  static Future<PersistentSchedulerComposition> open({
    String? sqlitePath,
    Clock? clock,
    CivilDate? selectedDate,
    FocusFlowFileLogger? logger,
    FocusFlowUiTimeZone? uiTimeZone,
  }) async {
    final resolvedLogger = logger ?? FocusFlowFileLogger.disabled();
    final resolvedTimeZone = uiTimeZone ?? FocusFlowUiTimeZone.utc;
    final resolvedSqlitePath = sqlitePath ?? sqlitePathFromEnvironment();
    scheduler_core.logger.info(
      () =>
          'Opening SQLite runtime. sqlitePath=$resolvedSqlitePath '
          'uiTimeZone=${resolvedTimeZone.code}',
    );
    final runtime = await SqliteApplicationRuntime.open(
      path: resolvedSqlitePath,
    );
    final bootstrap = await runtime.bootstrap();
    if (bootstrap.isFailure) {
      await runtime.close();
      scheduler_core.logger.error(
        () =>
            'SQLite bootstrap failed. failureCode=${bootstrap.failure!.code.name}',
        error: bootstrap.failure!.code.name,
      );
      throw StateError(
        'SQLite bootstrap failed: ${bootstrap.failure!.code.name}',
      );
    }
    scheduler_core.logger.debug(() => 'SQLite bootstrap completed.');
    final resolvedClock = clock ?? const SystemClock();
    final openedAt = resolvedClock.now();
    final date = selectedDate ?? resolvedTimeZone.toLocalCivilDate(openedAt);
    final readAt = openedAt.toUtc();
    final resolver = FixedOffsetTimeZoneResolver(
      offset: resolvedTimeZone.utcOffset,
    );
    scheduler_core.logger.info(
      () =>
          'Persistent scheduler composition opened. date=${date.toIsoString()} '
          'readAt=${readAt.toIso8601String()} uiTimeZone=${resolvedTimeZone.code}',
    );

    return PersistentSchedulerComposition._(
      runtime: runtime,
      todayQuery: GetTodayStateQuery(
        applicationStore: runtime.applicationStore,
        timeZoneResolver: resolver,
      ),
      managementUseCases: V1ApplicationManagementUseCases(
        applicationStore: runtime.applicationStore,
      ),
      commandUseCases: V1ApplicationCommandUseCases(
        applicationStore: runtime.applicationStore,
        timeZoneResolver: resolver,
      ),
      date: date,
      readAt: readAt,
      logger: resolvedLogger,
      uiTimeZone: resolvedTimeZone,
      clock: resolvedClock,
      timeZoneResolver: resolver,
    );
  }

  /// Resolves the SQLite path from Dart defines or the local dev default.
  static String sqlitePathFromEnvironment() {
    const configured = String.fromEnvironment('SCHEDULER_SQLITE_PATH');
    final trimmed = configured.trim();
    if (trimmed.isNotEmpty) {
      return trimmed;
    }
    final home =
        Platform.environment['HOME'] ?? Platform.environment['USERPROFILE'];
    if (home == null || home.trim().isEmpty) {
      return 'scheduler.sqlite';
    }
    return '$home/ADHD_Scheduler/scheduler.sqlite';
  }

  /// SQLite runtime owning the database handle.
  final SqliteApplicationRuntime runtime;

  /// Query object used to read the Today state.
  final GetTodayStateQuery todayQuery;

  /// Management use cases used by backlog-facing UI surfaces.
  final V1ApplicationManagementUseCases managementUseCases;

  /// Command use cases used by interactive UI controls.
  final V1ApplicationCommandUseCases commandUseCases;

  /// Local date represented by the app.
  final CivilDate date;

  /// Stable read clock instant for operation contexts.
  final DateTime readAt;

  /// Optional app runtime logger.
  final FocusFlowFileLogger logger;

  /// UI-local fixed-offset time-zone reference for wall-clock behavior.
  final FocusFlowUiTimeZone uiTimeZone;

  /// Runtime clock used to derive current UTC instants.
  final Clock clock;

  /// Resolver configured from [uiTimeZone].
  final FixedOffsetTimeZoneResolver timeZoneResolver;

  /// Pending close operation, if disposal has started.
  Future<void>? _disposeFuture;

  /// Default project id used by quick capture.
  @override
  String get defaultProjectId => SqliteApplicationRuntime.defaultV1ProjectId;

  /// Human-readable label for [date].
  @override
  String get dateLabel => dateLabelFor(date);

  /// Initial selected owner-local planning date.
  @override
  CivilDate get initialDate => date;

  /// Formats [date] for compact UI date labels.
  @override
  String dateLabelFor(CivilDate date) {
    return formatSchedulerDateLabel(date);
  }

  /// Creates the controller that owns the visible planning date.
  @override
  SelectedDateController createSelectedDateController() {
    scheduler_core.logger.finer(
      () =>
          'Creating selected date controller. date=${initialDate.toIsoString()}',
    );
    return SelectedDateController(initialDate: initialDate);
  }

  /// Creates the controller used by the compact Today screen mockup.
  @override
  TodayScreenController createTodayScreenController({
    required SelectedDateController selectedDates,
  }) {
    scheduler_core.logger.finer(
      () => 'Creating today screen controller. date=${date.toIsoString()}',
    );
    return TodayScreenController(
      selectedDates: selectedDates,
      dateLabelFor: dateLabelFor,
      now: _utcNow,
      timeZoneOffset: uiTimeZone.utcOffset,
      read: (date) {
        return todayQuery.execute(
          GetTodayStateRequest(
            context: context('ui-plan-1-read-today'),
            date: date,
            includeNextFlexibleItem: true,
          ),
        );
      },
    );
  }

  /// Creates the controller used by the Backlog board screen.
  @override
  BacklogBoardController createBacklogBoardController({
    required SelectedDateController selectedDates,
  }) {
    scheduler_core.logger.finer(
      () => 'Creating backlog board controller. date=${date.toIsoString()}',
    );
    return BacklogBoardController(
      selectedDates: selectedDates,
      dateLabelFor: dateLabelFor,
      readBoard: (request) {
        return managementUseCases.getBacklogBoard(
          GetBacklogBoardRequest(
            context: context('ui-read-backlog-board'),
            localDate: request.localDate,
            searchText: request.searchText,
            filters: request.filters,
            boardFilters: request.boardFilters,
            sortKey: request.sortKey,
            groupMode: request.groupMode,
          ),
        );
      },
      readTaskDetail: (taskId, localDate) {
        return managementUseCases.getBacklogTaskDetail(
          GetBacklogTaskDetailRequest(
            context: context('ui-read-backlog-detail'),
            localDate: localDate,
            taskId: taskId,
          ),
        );
      },
    );
  }

  /// Creates the command controller used by interactive scheduler controls.
  @override
  SchedulerCommandController createCommandController({
    required ReadRefresh refreshReads,
    required SelectedDateReader selectedDate,
  }) {
    scheduler_core.logger.finer(
      () =>
          'Creating scheduler command controller. operationIdPrefix=ui-command-${readAt.microsecondsSinceEpoch}',
    );
    return SchedulerCommandController(
      commands: commandUseCases,
      contextFor: context,
      selectedDate: selectedDate,
      refreshReads: refreshReads,
      quickCaptureProjectId: defaultProjectId,
      operationIdPrefix: 'ui-command-${readAt.microsecondsSinceEpoch}',
      currentDateFor: uiTimeZone.toLocalCivilDate,
      now: _utcNow,
    );
  }

  /// Creates an application operation context for [operationId].
  ApplicationOperationContext context(String operationId, {DateTime? now}) {
    return ApplicationOperationContext.start(
      operationId: operationId,
      ownerTimeZone: OwnerTimeZoneContext(
        ownerId: SqliteApplicationRuntime.defaultV1OwnerId,
        timeZoneId: uiTimeZone.code,
      ),
      clock: FixedClock((now ?? _utcNow()).toUtc()),
      idGenerator: SequentialIdGenerator(prefix: operationId),
    );
  }

  /// Closes the persistent runtime.
  @override
  Future<void> dispose() {
    return _disposeFuture ??= _dispose();
  }

  /// Closes runtime resources and records shutdown diagnostics when enabled.
  Future<void> _dispose() async {
    scheduler_core.logger.debug(() => 'Closing SQLite runtime.');
    await runtime.close();
    scheduler_core.logger.info(() => 'SQLite runtime closed.');
  }

  /// Returns the current runtime instant normalized to UTC.
  DateTime _utcNow() {
    return clock.now().toUtc();
  }
}
