/**
 * @file DestructiveLaneModal.tsx
 * @description Fetches and displays the exact lane lifecycle preflight facts
 * before delegating confirmation controls and accessibility to ConfirmModal.
 *
 * The server is the authority on what is permitted; this modal must never be
 * stricter than it. A blocker that does not actually stop the chosen action is
 * rendered as context (see HARD_BLOCKERS and noticesFor), not as a refusal —
 * treating every blocker as fatal is what left "Forget" dead for adopted lanes.
 * @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
 */

import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { ConfirmModal } from "../ConfirmModal";
import { api } from "../../lib/api";
import type { Lane, LanePreflight } from "../../lib/types";

type DestructiveAction = "reset" | "remove" | "purge";

export interface DestructiveLaneModalProps {
  lane: Lane;
  action: DestructiveAction;
  open: boolean;
  onClose: () => void;
  onConfirm: (body: { expect: Record<string, string | number | null>; force?: true }) => void;
}

function expectFor(preflight: LanePreflight): Record<string, string | number | null> {
  if (preflight.action === "purge") {
    return {
      sessions: preflight.sessions,
      events: preflight.events,
      tokenRows: preflight.tokenRows,
    };
  }
  return {
    head: preflight.head,
    dirty: preflight.dirty,
    untracked: preflight.untracked,
    unpushed: preflight.unpushed,
  };
}

/**
 * Which preflight blockers genuinely prevent each action in the UI.
 *
 * The server is the authority, and it permits EVERY shape of `remove`: an
 * adopted lane's record is forgotten with its directory untouched, a
 * hand-deleted worktree takes the prune path, an unreadable one is force
 * -removed. So `remove` is blocked here by nothing — treating `adopted` or
 * `missing` as blockers left the Forget button permanently dead with no
 * fallback. `reset` really is impossible in all three shapes.
 *
 * `unpushed-commits` is force-overridable and is gated by the Force checkbox
 * instead of by this list.
 */
const HARD_BLOCKERS: Record<"reset" | "remove", readonly string[]> = {
  reset: ["adopted", "missing", "unreadable"],
  remove: [],
};

function blockingReason(preflight: LanePreflight | null): string | null {
  if (!preflight || preflight.action === "purge") return null;
  const hard = HARD_BLOCKERS[preflight.action];
  return preflight.blocked.find((blocker) => hard.includes(blocker)) || null;
}

/**
 * Blockers that do not prevent THIS action but still change what it does, shown
 * as context rather than as an obstacle — e.g. forgetting an adopted lane leaves
 * its directory alone. `unpushed-commits` is excluded: the Force copy covers it.
 */
function noticesFor(preflight: LanePreflight | null): string[] {
  if (!preflight || preflight.action === "purge") return [];
  const hard = HARD_BLOCKERS[preflight.action];
  return preflight.blocked.filter(
    (blocker) => blocker !== "unpushed-commits" && !hard.includes(blocker)
  );
}

/** Rough byte estimate as a short human string; the server's own number is a guess. */
function formatBytes(bytes: number): string {
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

function warningsFor(preflight: LanePreflight | null): string[] {
  if (!preflight || preflight.action === "purge") return [];
  return preflight.warnings;
}

export function DestructiveLaneModal({
  lane,
  action,
  open,
  onClose,
  onConfirm,
}: DestructiveLaneModalProps) {
  const { t } = useTranslation(["lanes"]);
  const [preflight, setPreflight] = useState<LanePreflight | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [force, setForce] = useState(false);

  useEffect(() => {
    if (!open) return;
    let current = true;
    setLoading(true);
    setError(null);
    setPreflight(null);
    setForce(false);
    void api.lanes
      .preflight(lane.id, action)
      .then((result) => current && setPreflight(result))
      .catch((err) => current && setError(err instanceof Error ? err.message : t("preflightError")))
      .finally(() => current && setLoading(false));
    return () => {
      current = false;
    };
  }, [action, lane.id, open, t]);

  const blocked = blockingReason(preflight);
  // Mirrors the server's gate exactly (server/routes/lanes.js): force is required
  // for `reset`, and for `remove` only when a real worktree is at risk. Forgetting
  // an adopted lane risks nothing on disk, so it needs no force even with unpushed
  // commits. Never offered alongside a hard blocker, where confirming is
  // impossible anyway.
  const requiresForce =
    !blocked &&
    preflight !== null &&
    preflight.action !== "purge" &&
    preflight.blocked.includes("unpushed-commits") &&
    (preflight.action === "reset" || preflight.kind === "managed");
  const disabled =
    loading || !preflight || Boolean(error) || Boolean(blocked) || (requiresForce && !force);
  const facts = preflight ? Object.entries(expectFor(preflight)) : [];
  const purge = preflight?.action === "purge" ? preflight : null;

  return (
    <ConfirmModal
      open={open}
      title={t(`destructive.${action}.title`)}
      message={t(`destructive.${action}.message`)}
      confirmLabel={t(`destructive.${action}.confirm`)}
      cancelLabel={t("destructive.cancel")}
      busy={loading}
      disabled={disabled}
      onCancel={onClose}
      onConfirm={() => {
        if (!preflight || disabled) return;
        onConfirm({ expect: expectFor(preflight), ...(force ? { force: true as const } : {}) });
      }}
    >
      {loading && <p className="mt-3 text-xs text-neutral-400">{t("destructive.loading")}</p>}
      {error && (
        <p className="mt-3 text-xs text-red-300">
          {t("preflightErrorWithMessage", { message: error })}
        </p>
      )}
      {preflight && (
        <table className="mt-3 w-full text-left text-xs text-neutral-300">
          <tbody>
            {facts.map(([name, value]) => (
              <tr key={name} className="border-t border-neutral-800">
                <th scope="row" className="py-1.5 font-medium text-neutral-400">
                  {t(`destructive.count.${name}`)}
                </th>
                <td className="py-1.5 text-right tabular-nums">{value ?? "—"}</td>
              </tr>
            ))}
            {/* Not part of `expect`: an estimate the server never verifies. */}
            {purge && (
              <tr className="border-t border-neutral-800">
                <th scope="row" className="py-1.5 font-medium text-neutral-400">
                  {t("destructive.count.bytesEstimate")}
                </th>
                <td className="py-1.5 text-right tabular-nums">
                  {formatBytes(purge.bytesEstimate)}
                </td>
              </tr>
            )}
          </tbody>
        </table>
      )}
      {blocked && (
        <p className="mt-3 rounded border border-amber-700/60 bg-amber-950/30 px-2 py-1.5 text-xs text-amber-200">
          {t(`destructive.blocked.${blocked}`)}
        </p>
      )}
      {purge?.activeSessionSkipped && (
        <p className="mt-3 rounded border border-neutral-700 bg-neutral-800/50 px-2 py-1.5 text-xs text-neutral-300">
          {t("destructive.notice.activeSessionSkipped")}
        </p>
      )}
      {noticesFor(preflight).map((notice) => (
        <p
          key={notice}
          className="mt-3 rounded border border-neutral-700 bg-neutral-800/50 px-2 py-1.5 text-xs text-neutral-300"
        >
          {t(`destructive.notice.${notice}`)}
        </p>
      ))}
      {warningsFor(preflight).map((warning) => (
        <p
          key={warning}
          className="mt-3 rounded border border-neutral-700 bg-neutral-800/50 px-2 py-1.5 text-xs text-neutral-300"
        >
          {t(`destructive.warning.${warning}`)}
        </p>
      ))}
      {requiresForce && (
        <label className="mt-3 flex items-start gap-2 text-xs text-amber-200">
          <input
            type="checkbox"
            checked={force}
            onChange={(event) => setForce(event.target.checked)}
            className="mt-0.5"
          />
          {t("destructive.force")}
        </label>
      )}
      {action === "reset" && (
        <p className="mt-3 text-xs text-neutral-400">{t("destructive.reset.survives")}</p>
      )}
    </ConfirmModal>
  );
}
