/**
 * `AppExitConfirmDialog` — app-wide confirmation shown when closing the main
 * window would interrupt active work (ticket #83). Mounted once near the App
 * root, alongside `AnnouncementsProvider`; subscribes to the backend's
 * `app-exit-work-guard` event via {@link SystemGateway.onAppExitWorkGuard} and
 * renders nothing until the guard actually fires.
 *
 * The backend owns the decision: it intercepts `WindowEvent::CloseRequested`
 * on the `main` window, aggregates active work across every open project, and
 * only emits the guard event when closing would interrupt something. This
 * component is purely presentational on top of that signal — it never derives
 * "work in progress" itself from local state, which would risk missing work
 * in a project/tab the frontend hasn't refreshed.
 *
 * `Quitter quand même` calls `confirmAppExit()`, which bypasses the guard once
 * and re-requests the real main-window close (same teardown order as an
 * unguarded quit). `Annuler` only clears local dialog state — no backend call,
 * the window and every session stay untouched.
 */

import { useEffect, useRef, useState } from "react";

import type { AppExitWorkGuardDetail, AppExitWorkGuardState } from "@/domain";
import { useGateways } from "@/app/di";
import { Button, zIndex } from "@/shared";

const MAX_DETAIL_LINES = 5;

/** The narrative body sentence, per carnet #83's exact wording rules. */
function bodyText(state: AppExitWorkGuardState): string {
  if (state.totalWorkCount === 1) {
    return "1 travail actif sera interrompu si vous quittez IdeA maintenant.";
  }
  const parts: string[] = [];
  if (state.busyAgentCount > 0) {
    parts.push(
      state.busyAgentCount === 1
        ? "1 agent travaille encore"
        : `${state.busyAgentCount} agents travaillent encore`,
    );
  }
  if (state.activeBackgroundTaskCount > 0) {
    parts.push(
      state.activeBackgroundTaskCount === 1
        ? "1 tâche de fond est encore active"
        : `${state.activeBackgroundTaskCount} tâches de fond sont encore actives`,
    );
  }
  return `${parts.join(" et ")}. Ces travaux seront interrompus si vous quittez IdeA maintenant.`;
}

/** One compact detail line, per carnet #83's exact line format. */
function detailLine(detail: AppExitWorkGuardDetail): string {
  if (detail.kind === "busyAgent") {
    return `Agent ${detail.agentName} — ${detail.projectName}`;
  }
  const shortTaskId = detail.taskId.slice(0, 8);
  return `Tâche de fond ${shortTaskId} — ${detail.projectName}`;
}

function describeError(e: unknown): string {
  if (e && typeof e === "object" && "message" in e) {
    return String((e as { message: unknown }).message);
  }
  return String(e);
}

const TITLE_ID = "app-exit-confirm-title";
const DESC_ID = "app-exit-confirm-desc";

export function AppExitConfirmDialog() {
  const { system } = useGateways();
  const [guard, setGuard] = useState<AppExitWorkGuardState | null>(null);
  const [closing, setClosing] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const cancelRef = useRef<HTMLButtonElement>(null);
  const dialogRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    let cancelled = false;
    let unsub: (() => void) | undefined;
    system
      .onAppExitWorkGuard((state) => {
        // Any emission implies the backend just intercepted a close with work
        // in progress. If the dialog is already open, this refreshes the
        // summary in place rather than closing/reopening it (carnet #83: the
        // popup never auto-closes on its own).
        setGuard(state);
      })
      .then((u) => {
        if (cancelled) u();
        else unsub = u;
      })
      .catch(() => {
        /* guard relay unavailable in this environment (e.g. focused test DI) */
      });
    return () => {
      cancelled = true;
      unsub?.();
    };
  }, [system]);

  // Mount-only focus: an open dialog focuses the safe choice once. Re-running
  // on every render (e.g. a refreshed summary) would steal focus back from
  // wherever the user tabbed to.
  useEffect(() => {
    if (guard) cancelRef.current?.focus();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [guard != null]);

  function cancel() {
    if (closing) return;
    setGuard(null);
    setError(null);
  }

  async function confirmExit() {
    setClosing(true);
    setError(null);
    try {
      await system.confirmAppExit();
    } catch (e) {
      setError(describeError(e));
    } finally {
      setClosing(false);
    }
  }

  useEffect(() => {
    if (!guard) return;
    function onKeyDown(e: KeyboardEvent) {
      if (e.key === "Escape") {
        e.preventDefault();
        cancel();
        return;
      }
      if (e.key !== "Tab") return;
      // Simple focus trap: wrap Tab/Shift+Tab within the dialog's focusable set.
      const focusables = dialogRef.current?.querySelectorAll<HTMLElement>(
        'button:not(:disabled), [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
      );
      if (!focusables || focusables.length === 0) return;
      const first = focusables[0];
      const last = focusables[focusables.length - 1];
      if (e.shiftKey && document.activeElement === first) {
        e.preventDefault();
        last.focus();
      } else if (!e.shiftKey && document.activeElement === last) {
        e.preventDefault();
        first.focus();
      }
    }
    window.addEventListener("keydown", onKeyDown);
    return () => window.removeEventListener("keydown", onKeyDown);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [guard != null, closing]);

  if (!guard) return null;

  const shown = guard.details.slice(0, MAX_DETAIL_LINES);
  const remaining = guard.details.length - shown.length;

  return (
    // No `onClick` here: clicking outside the dialog is deliberately a no-op
    // (carnet #83 — avoid an ambiguous accidental dismissal of a destructive
    // confirmation).
    <div
      className="fixed inset-0 flex items-center justify-center bg-black/50 p-4"
      style={{ zIndex: zIndex.toast }}
    >
      <div
        ref={dialogRef}
        role="alertdialog"
        aria-modal="true"
        aria-labelledby={TITLE_ID}
        aria-describedby={DESC_ID}
        onClick={(e) => e.stopPropagation()}
        className="flex w-full max-w-[520px] min-w-[440px] flex-col gap-3 rounded-lg border border-border bg-raised p-4 shadow-xl"
      >
        <h3 id={TITLE_ID} className="text-sm font-semibold text-content">
          Du travail est encore en cours
        </h3>
        <p id={DESC_ID} className="text-sm text-content">
          {bodyText(guard)}
        </p>
        <p className="text-sm text-muted">
          Annulez la fermeture pour laisser les agents et les tâches se terminer.
        </p>

        {shown.length > 0 && (
          <ul className="flex flex-col gap-1 rounded-md bg-canvas p-2 text-xs text-muted">
            {shown.map((detail, i) => (
              <li key={i}>{`• ${detailLine(detail)}`}</li>
            ))}
            {remaining > 0 && <li>+ {remaining} autre{remaining > 1 ? "s" : ""}</li>}
          </ul>
        )}

        {error && (
          <p role="alert" className="text-sm text-danger">
            IdeA n'a pas pu quitter correctement. Réessayez ou consultez les logs.
          </p>
        )}

        <div className="flex justify-end gap-2 pt-1">
          <Button ref={cancelRef} size="sm" variant="ghost" disabled={closing} onClick={cancel}>
            Annuler
          </Button>
          <Button
            size="sm"
            variant="danger"
            disabled={closing}
            loading={closing}
            onClick={() => void confirmExit()}
          >
            {closing ? "Fermeture…" : "Quitter quand même"}
          </Button>
        </div>
      </div>
    </div>
  );
}
