import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';

import '../../about/external/data/Commitment.dart';
import '../../about/external/data/ExcuseCluster.dart';
import '../../about/internal/application/CommitmentClass.dart';
import '../../about/internal/application/CommitmentStatus.dart';
import '../../about/internal/application/NavigatorType.dart';
import '../../about/internal/application/NotificationType.dart';
import '../../about/internal/application/Standing.dart';
import '../../about/internal/application/TextType.dart';
import '../../about/internal/application/UserDetails.dart';
import '../../configs/Navigator.dart';
import '../../designs/Checkbox.dart';
import '../../designs/Component.dart';
import '../../designs/Responsive.dart';
import '../../designs/Shell.dart';
import '../../designs/buttons/Buttons.dart';
import '../../designs/text/Text.dart';
import '../../utils/Colors.dart';
import '../../utils/CommonUtils.dart';
import '../../utils/StandingEngine.dart';
import '../../utils/Thresholds.dart';
import '../../utils/ToneEngine.dart';
import '../commitment/NewCommitment.dart';
import '../excuse/ExcuseReport.dart';
import '../goal/Goals.dart';
import '../overdue/OverdueQueue.dart';
import '../reportcard/ReportCardScreen.dart';
import '../settings/Settings.dart';
import '../training/Training.dart';
import 'ConnectHome.dart';
import 'Home.dart';
import 'ViewHome.dart';

class HomeState extends State<Home> implements ConnectHome {
  ViewHome? _model;

  UserDetails _user = UserDetails(pic: '', name: '');

  List<Commitment> _plan = <Commitment>[];

  List<Commitment> _overdue = <Commitment>[];

  Standing _standing = Standing.Good;

  double _debt = 0;

  ExcuseCluster? _insight;

  bool _distressed = false;

  /// Ids ticked locally but not yet confirmed by the server. The row reads as
  /// done immediately so the tick feels instant, and rolls back if the call
  /// fails.
  final Set<String> _ticked = <String>{};

  @override
  Widget build(BuildContext context) {
    return ViewModelBuilder<ViewHome>.reactive(
      viewModelBuilder: () => ViewHome(context, this),
      onViewModelReady: (viewModel) {
        _model = viewModel;
        _initiate();
      },
      builder: (context, viewModel, child) => LayoutBuilder(
        builder: (BuildContext context, BoxConstraints constraints) {
          return Responsive(
            mobile: _mobileView(constraints),
            tablet: _mobileView(constraints),
            desktop: _mobileView(constraints),
          );
        },
      ),
    );
  }

  void _initiate() {
    _model?.initialise();
  }

  // ── Handlers ──────────────────────────────────────────────────────────────

  void _onOpenOverdue() async {
    final result = await GroundedNavigation()
        .navigateToPageWithData(const OverdueQueue(), context);

    if (result == true) {
      _model?.loadPlan();
    }
  }

  void _onAddCommitment() async {
    if (!StandingEngine.permitsNewCommitment(_standing)) {
      _model?.requestNewCommitment(_standing, CommitmentClass.Standard);
      return;
    }

    final result = await GroundedNavigation()
        .navigateToPageWithData(const NewCommitment(), context);

    if (result == true) {
      _model?.loadPlan();
    }
  }

  void _onTick(Commitment item) {
    if (item.id == null || _ticked.contains(item.id)) {
      return;
    }

    setState(() {
      _ticked.add(item.id!);
    });

    _model?.tick(item);
  }

  void _onOpenReportCard() {
    GroundedNavigation().navigateToPage(
        NavigatorType.justOpen, const ReportCardScreen(), context);
  }

  void _onOpenGoals() async {
    final result = await GroundedNavigation()
        .navigateToPageWithData(const Goals(), context);

    if (result == true) {
      _model?.loadPlan();
    }
  }

  void _onOpenTraining() {
    GroundedNavigation()
        .navigateToPage(NavigatorType.justOpen, const Training(), context);
  }

  void _onOpenSettings() {
    GroundedNavigation()
        .navigateToPage(NavigatorType.justOpen, const Settings(), context);
  }

  void _onOpenExcuses() {
    GroundedNavigation()
        .navigateToPage(NavigatorType.justOpen, const ExcuseReport(), context);
  }

  // ── Views ─────────────────────────────────────────────────────────────────

  Widget _mobileView(BoxConstraints constraints) {
    // Grounded and Lockdown replace the home screen with the overdue queue —
    // you do not get to look at your nice plans, only at your mess.
    final bool queueIsHome =
        StandingEngine.showsOverdueQueueAsHome(_standing) && !_distressed;

    return Sheet(
      eyebrow: _user.name.isEmpty ? "Grounded" : _user.name,
      title: queueIsHome ? "What you owe" : "Today",
      chrome: _distressed ? colorPrimaryDark : _chromeFor(_standing),
      banner: _standingBanner(),
      action: chromeAction(
        CupertinoIcons.person,
        _onOpenSettings,
        dotted: _user.sickMode,
        dotColor: colorStandingWarned,
      ),
      child: _distressed
          ? _distressBody()
          : queueIsHome
              ? _groundedBody()
              : _planBody(),
    );
  }

  /// The chrome carries the standing colour, so the tier is legible before a
  /// single word is read.
  Color _chromeFor(Standing standing) {
    switch (standing) {
      case Standing.Good:
        return colorPrimaryDark;
      case Standing.Warned:
        return colorPrimaryDark;
      case Standing.Grounded:
        return colorStandingGrounded;
      case Standing.Lockdown:
        return colorStandingLockdown;
    }
  }

  /// The debt strip that sits in the black chrome under the title.
  Widget _standingBanner() {
    if (_distressed) {
      return const SizedBox.shrink();
    }

    final Color tone = standingColor(_standing);

    return Container(
      padding: const EdgeInsets.fromLTRB(16, 14, 16, 14),
      decoration: BoxDecoration(
        color: colorWhite.withValues(alpha: 0.08),
        borderRadius: BorderRadius.circular(16),
      ),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisSize: MainAxisSize.min,
              children: [
                Row(
                  children: [
                    Container(
                      width: 7,
                      height: 7,
                      decoration: BoxDecoration(
                        color: _standing == Standing.Good ? tone : colorWhite,
                        shape: BoxShape.circle,
                      ),
                    ),
                    const SizedBox(width: 8),
                    text(
                      standingLabel(_standing).toUpperCase(),
                      9,
                      TextType.Bold,
                      color: colorWhite.withValues(alpha: 0.75),
                      letterSpacing: 1.2,
                    ),
                  ],
                ),
                const SizedBox(height: 8),
                text(
                  ToneEngine.standingHeadline(_standing, _user.tone),
                  16,
                  TextType.Medium,
                  color: colorWhite,
                ),
              ],
            ),
          ),
          const SizedBox(width: 16),
          Column(
            crossAxisAlignment: CrossAxisAlignment.end,
            mainAxisSize: MainAxisSize.min,
            children: [
              text("DEBT", 9, TextType.Bold,
                  color: colorWhite.withValues(alpha: 0.45),
                  letterSpacing: 1.0),
              const SizedBox(height: 4),
              text(formatDebt(_debt), 30, TextType.Light, color: colorWhite),
            ],
          ),
        ],
      ),
    );
  }

  /// The normal day: the plan, with the overdue count kept visible above it so
  /// it is never out of sight.
  Widget _planBody() {
    final int overdueCount = _overdue.length;

    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      mainAxisSize: MainAxisSize.min,
      children: [
        if (overdueCount > 0) ...[
          _overdueCallout(overdueCount),
          const SizedBox(height: 24),
        ],
        if (_insight != null) ...[
          _insightCard(_insight!),
          const SizedBox(height: 24),
        ],
        sectionBreak(
          "The plan",
          caption: "${_plan.length} committed",
          trailing: _plan.isEmpty
              ? null
              : text(formatMinutes(_plannedMinutes()), 12, TextType.Bold,
                  color: colorGrey2),
        ),
        if (_plan.isEmpty)
          emptyState(
            CupertinoIcons.square_list,
            "Nothing committed today",
            "An empty plan is a decision too. Add something you actually intend to do.",
          )
        else ...[
          ..._plan.map(_commitmentRow),
          addTaskAffordance(
            _onAddCommitment,
            enabled: StandingEngine.permitsNewCommitment(_standing),
          ),
        ],
        const SizedBox(height: 12),
        _quickLinks(),
        const SizedBox(height: 24),
        roundedCornerButton(
          "Commit to something",
          _onAddCommitment,
          icon: CupertinoIcons.add,
          enabled: StandingEngine.permitsNewCommitment(_standing),
        ),
        if (!StandingEngine.permitsNewCommitment(_standing)) ...[
          const SizedBox(height: 10),
          text(
            ToneEngine.standingBody(_standing, _user.tone),
            12,
            TextType.Regular,
            color: colorGrey2,
            align: TextAlign.center,
          ),
        ],
      ],
    );
  }

  /// Grounded: the plan is hidden entirely and only the mess is shown.
  Widget _groundedBody() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      mainAxisSize: MainAxisSize.min,
      children: [
        text("YOUR PLANS ARE HIDDEN", 10, TextType.Bold,
            color: colorGrey2, letterSpacing: 1.2),
        const SizedBox(height: 10),
        displayTitle(
          _standing == Standing.Lockdown
              ? "One at a time."
              : "Clear this first.",
        ),
        const SizedBox(height: 12),
        text(
          ToneEngine.standingBody(_standing, _user.tone),
          14,
          TextType.Regular,
          color: colorGrey2,
          height: 1.55,
        ),
        const SizedBox(height: 28),
        card(
          background: standingBackground(_standing),
          borderColor: standingColor(_standing).withValues(alpha: 0.20),
          child: Row(
            children: [
              Expanded(
                child: labelled(
                  "Open overdue",
                  "${_overdue.length}",
                  valueSize: 26,
                  valueType: TextType.Light,
                  valueColor: standingColor(_standing),
                ),
              ),
              Expanded(
                child: labelled(
                  "Debt to clear",
                  formatDebt(
                      StandingEngine.debtToNextTierDown(_debt, _standing)),
                  valueSize: 26,
                  valueType: TextType.Light,
                  valueColor: standingColor(_standing),
                ),
              ),
            ],
          ),
        ),
        const SizedBox(height: 24),
        sectionBreak("Outstanding", caption: "${_overdue.length} items"),
        if (_overdue.isEmpty)
          emptyState(
            CupertinoIcons.checkmark_seal,
            "The queue is empty",
            "Your standing will recover as the debt decays.",
          )
        else
          ..._overdue.take(_standing == Standing.Lockdown ? 1 : _overdue.length)
              .map(_commitmentRow),
        const SizedBox(height: 24),
        roundedCornerButton(
          _standing == Standing.Lockdown ? "Deal with this one" : "Open the queue",
          _onOpenOverdue,
          background: standingColor(_standing),
          icon: CupertinoIcons.arrow_right,
        ),
      ],
    );
  }

  /// Distress: the strict persona drops entirely. This is the difference
  /// between a product people keep and one they resent.
  Widget _distressBody() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      mainAxisSize: MainAxisSize.min,
      children: [
        text("A NOTE", 10, TextType.Bold, color: colorGrey2, letterSpacing: 1.2),
        const SizedBox(height: 10),
        displayTitle(ToneEngine.distressHeadline()),
        const SizedBox(height: 14),
        text(
          ToneEngine.distressBody(),
          15,
          TextType.Regular,
          color: colorGrey2,
          height: 1.6,
        ),
        const SizedBox(height: 28),
        card(
          background: colorStandingGoodBg,
          borderColor: colorPositive.withValues(alpha: 0.20),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisSize: MainAxisSize.min,
            children: [
              text("PAUSED", 9, TextType.Bold,
                  color: colorPositive, letterSpacing: 1.2),
              const SizedBox(height: 8),
              text("Debt is not accruing right now.", 16, TextType.Medium,
                  color: colorPrimaryDark),
              const SizedBox(height: 6),
              text(
                "Nothing you miss this week is counting against you.",
                13,
                TextType.Regular,
                color: colorGrey2,
                height: 1.5,
              ),
            ],
          ),
        ),
        const SizedBox(height: 24),
        sectionBreak("Three things", caption: "that actually matter"),
        ..._plan
            .where((item) =>
                item.commitmentClass == CommitmentClass.NonNegotiable)
            .take(3)
            .map(_commitmentRow),
        const SizedBox(height: 24),
        outlinedActionButton("Open settings", _onOpenSettings,
            icon: CupertinoIcons.slider_horizontal_3),
      ],
    );
  }

  Widget _overdueCallout(int count) {
    return card(
      background: colorStandingGroundedBg,
      borderColor: colorStandingGrounded.withValues(alpha: 0.20),
      onTap: _onOpenOverdue,
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Container(
            width: 44,
            height: 44,
            alignment: Alignment.center,
            decoration: BoxDecoration(
              color: colorStandingGrounded,
              borderRadius: BorderRadius.circular(13),
            ),
            child: text("$count", 17, TextType.Bold, color: colorWhite),
          ),
          const SizedBox(width: 14),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisSize: MainAxisSize.min,
              children: [
                text("OVERDUE", 9, TextType.Bold,
                    color: colorStandingGrounded, letterSpacing: 1.2),
                const SizedBox(height: 5),
                text(
                  count >= Thresholds.maxOpenOverdue
                      ? "You are at the cap. Nothing new until this drops."
                      : "$count item${count == 1 ? "" : "s"} past the window.",
                  14,
                  TextType.Medium,
                  color: colorPrimaryDark,
                ),
              ],
            ),
          ),
          Icon(CupertinoIcons.chevron_right,
              size: 15, color: colorStandingGrounded),
        ],
      ),
    );
  }

  /// The excuse confrontation. One pattern, stated plainly, with the
  /// suggestion attached.
  Widget _insightCard(ExcuseCluster cluster) {
    return card(
      background: colorPrimaryDark,
      borderColor: colorPrimaryDark,
      onTap: _onOpenExcuses,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisSize: MainAxisSize.min,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              text("PATTERN", 9, TextType.Bold,
                  color: colorWhite.withValues(alpha: 0.45),
                  letterSpacing: 1.2),
              text("${cluster.occurrences}×", 11, TextType.Bold,
                  color: colorWhite.withValues(alpha: 0.45)),
            ],
          ),
          const SizedBox(height: 12),
          text(cluster.insight, 15, TextType.Regular,
              color: colorWhite, height: 1.55),
        ],
      ),
    );
  }

  /// A task line with its tick box. Completed items keep their place, struck
  /// through — the day stays a record of what was asked, not just what is left.
  Widget _commitmentRow(Commitment item) {
    final bool settled = item.status == CommitmentStatus.Completed ||
        item.status == CommitmentStatus.LateCompleted;

    final bool ticked = settled || _ticked.contains(item.id);

    final bool late = item.windowClosed && !settled;

    final Color accent = classColor(item.commitmentClass);

    return Container(
      margin: const EdgeInsets.only(bottom: 6),
      child: card(
        padding: const EdgeInsets.fromLTRB(12, 8, 14, 8),
        child: TickRow(
          checked: ticked,
          title: item.title,
          accent: item.status == CommitmentStatus.LateCompleted
              ? colorStandingWarned
              : accent,
          onTick: settled ? null : () => _onTick(item),
          onTap: _onOpenOverdue,
          detail: Row(
            children: [
              text(formatWindow(item), 11, TextType.Regular,
                  color: colorGrey2),
              const SizedBox(width: 10),
              Container(
                width: 3,
                height: 3,
                decoration:
                    BoxDecoration(color: colorGrey, shape: BoxShape.circle),
              ),
              const SizedBox(width: 10),
              text(formatMinutes(item.estMinutes), 11, TextType.Regular,
                  color: colorGrey2),
              if (late) ...[
                const SizedBox(width: 10),
                pill(
                  overdueLabel(item),
                  colorStandingGrounded,
                  colorStandingGroundedBg,
                  textSize: 9,
                ),
              ],
              if (item.status == CommitmentStatus.LateCompleted) ...[
                const SizedBox(width: 10),
                pill("Late", colorStandingWarned, colorStandingWarnedBg,
                    textSize: 9),
              ],
            ],
          ),
          trailing: ticked
              ? null
              : pill(
                  classLabel(item.commitmentClass),
                  accent,
                  classBackground(item.commitmentClass),
                  textSize: 9,
                ),
        ),
      ),
    );
  }

  Widget _quickLinks() {
    return Row(
      children: [
        Expanded(
          child: _quickLink(
            CupertinoIcons.flag_fill,
            "Goals",
            _onOpenGoals,
          ),
        ),
        const SizedBox(width: 10),
        Expanded(
          child: _quickLink(
            CupertinoIcons.chart_bar_alt_fill,
            "Report",
            _onOpenReportCard,
          ),
        ),
        const SizedBox(width: 10),
        Expanded(
          child: _quickLink(
            CupertinoIcons.flame_fill,
            "Training",
            _onOpenTraining,
          ),
        ),
      ],
    );
  }

  Widget _quickLink(IconData icon, String label, VoidCallback onTap) {
    return card(
      padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 16),
      onTap: onTap,
      child: Row(
        children: [
          Icon(icon, size: 17, color: colorPrimaryDark),
          const SizedBox(width: 10),
          Expanded(
            child: text(label, 13, TextType.Medium,
                color: colorPrimaryDark, maxLines: 1,
                overflow: TextOverflow.ellipsis),
          ),
        ],
      ),
    );
  }

  double _plannedMinutes() {
    double total = 0;
    for (Commitment item in _plan) {
      total = total + item.estMinutes;
    }
    return total;
  }

  // ── ConnectHome ───────────────────────────────────────────────────────────

  @override
  void onUserLoaded(UserDetails details) {
    setState(() {
      _user = details;
    });
  }

  @override
  void onPlanLoaded(List<Commitment> plan) {
    setState(() {
      _plan = plan;
      // The server is now authoritative; anything it reports as settled no
      // longer needs the local optimistic flag.
      _ticked.removeWhere((id) => plan.every((item) => item.id != id));
    });
  }

  @override
  void onOverdueLoaded(List<Commitment> overdue) {
    setState(() {
      _overdue = overdue;
    });
  }

  @override
  void onStandingResolved(Standing standing, double debtScore) {
    setState(() {
      _standing = standing;
      _debt = debtScore;
    });
  }

  @override
  void onExcuseInsight(ExcuseCluster? cluster) {
    setState(() {
      _insight = cluster;
    });
  }

  @override
  void onDistressDetected() {
    setState(() {
      _distressed = true;
    });
  }

  @override
  void onTicked(Commitment item, bool late) {
    if (!late) {
      return;
    }

    // A clean tick needs no comment. A late one does — otherwise the user
    // learns that the window never mattered.
    _model?.showApplicationNotification(
      NotificationType.warning,
      "Late complete",
      "The window had already closed, so this is recorded as a late complete. It reduces the debt but does not clear it.",
      true,
      true,
      null,
    );
  }

  @override
  void onTickFailed(Commitment item) {
    setState(() {
      _ticked.remove(item.id);
    });
  }

  @override
  void onCreationBlocked(String reason) {
    _model?.showApplicationNotification(
      NotificationType.warning,
      "Not right now",
      reason,
      true,
      true,
      null,
    );
  }
}
