import {
  ErrorCode,
  type HouseView,
  renameHouseSchema,
  type SourceView,
} from "@batch-cooking/shared";
import { type FormEvent, useCallback, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { ApiError, apiClient } from "../../api/client";
import { useAuth } from "../../features/auth/AuthContext";
import { SourceSelect } from "../../features/house/SourceSelect";
import { HouseNameField } from "../../features/profile/HouseNameField";
import { fieldErrorsFrom } from "../../lib/zod-errors";
import { errorMessageService } from "../../services/error-message.service";
import "./settings-pages.scss";

/** Status of the household name's own autosave — see `PreferencesPage` for the same hot-saving pattern. */
type SaveState = "idle" | "saving" | "saved" | "error";

/** Debounce for the household name field (typing) — long enough that saves don't fire on every keystroke. */
const HOUSE_NAME_DEBOUNCE_MS = 600;

/** Debounce for the enabled-sources checkboxes — same reasoning as `PreferencesPage`'s `ALLERGIES_DEBOUNCE_MS`: coalesces a quick burst of toggles into one request. */
const HOUSE_SOURCES_DEBOUNCE_MS = 500;

/**
 * Household settings — routed at `/parametres/foyer`. Split out of what
 * used to be `HouseholdPage` (regime/allergies moved to `PreferencesPage`,
 * a personal-profile concern rather than a household one). Two very
 * different layouts depending on whether the profile currently belongs to
 * a household:
 *
 * - **No household**: create one, or join an existing one by invite code.
 * - **Has a household**: rename it (hot-save, same pattern as before),
 *   share its invite code, see its members, and either manage it (admin:
 *   remove a member, delete the household) or leave it (non-admin).
 *
 * Reloads `getCurrentHouse()` after every mutation (create/join/leave/
 * delete/remove) rather than optimistically patching local state — these
 * are infrequent, deliberate actions, not a hot-saved field, so the extra
 * round trip isn't worth the risk of the two ever drifting apart.
 */
export function HouseholdSettingsPage() {
  const { t } = useTranslation();
  const { user } = useAuth();

  const [isLoading, setIsLoading] = useState(true);
  const [loadError, setLoadError] = useState(false);
  const [house, setHouse] = useState<HouseView | null>(null);

  // Stable across renders (empty deps — only closes over setters, which
  // React itself guarantees are stable) so the effect below only re-runs on
  // mount, not on every render. It used to be a plain function declaration,
  // a fresh reference every render — its `useEffect` dependency on it then
  // re-triggered the effect (and thus the fetch, and thus a state update,
  // and thus another render...) forever. `loadHouse` is also handed to
  // `NoHousehold`/`HasHousehold` below as their `onChanged` callback, so it
  // has to stay this stable regardless of the effect.
  const loadHouse = useCallback(() => {
    setIsLoading(true);
    setLoadError(false);
    return apiClient
      .getCurrentHouse()
      .then((result) => setHouse(result))
      .catch(() => setLoadError(true))
      .finally(() => setIsLoading(false));
  }, []);

  useEffect(() => {
    void loadHouse();
  }, [loadHouse]);

  if (isLoading) {
    return (
      <div className="settings-page">
        <h1>{t("household.title")}</h1>
        <p className="settings-page__status">{t("onboarding.loading")}</p>
      </div>
    );
  }

  if (loadError) {
    return (
      <div className="settings-page">
        <h1>{t("household.title")}</h1>
        <p className="settings-page__status settings-page__status--error">
          {t("common.loadError")}
        </p>
      </div>
    );
  }

  return (
    <div className="settings-page">
      <h1>{t("household.title")}</h1>
      {house === null ? (
        <NoHousehold onChanged={loadHouse} />
      ) : (
        <HasHousehold house={house} currentUserId={user?.id ?? null} onChanged={loadHouse} />
      )}
    </div>
  );
}

/** Create-or-join forms shown when the profile doesn't belong to a household yet. */
function NoHousehold({ onChanged }: { onChanged: () => void }) {
  const { t } = useTranslation();

  const [name, setName] = useState("");
  const [nameErrors, setNameErrors] = useState<Record<string, string>>({});
  const [createError, setCreateError] = useState<string | null>(null);
  const [isCreating, setIsCreating] = useState(false);

  const [inviteCode, setInviteCode] = useState("");
  const [joinError, setJoinError] = useState<string | null>(null);
  const [isJoining, setIsJoining] = useState(false);

  async function handleCreate(e: FormEvent) {
    e.preventDefault();
    setCreateError(null);
    const result = renameHouseSchema.safeParse({ name });
    if (!result.success) {
      setNameErrors(fieldErrorsFrom(result.error));
      return;
    }
    setNameErrors({});
    setIsCreating(true);
    try {
      await apiClient.createHouse(result.data.name);
      onChanged();
    } catch (err) {
      const code = err instanceof ApiError ? err.code : ErrorCode.INTERNAL_ERROR;
      setCreateError(errorMessageService.getLabel(code));
    } finally {
      setIsCreating(false);
    }
  }

  async function handleJoin(e: FormEvent) {
    e.preventDefault();
    setJoinError(null);
    setIsJoining(true);
    try {
      await apiClient.joinHouse(inviteCode.trim());
      onChanged();
    } catch (err) {
      const code = err instanceof ApiError ? err.code : ErrorCode.INTERNAL_ERROR;
      setJoinError(errorMessageService.getLabel(code));
    } finally {
      setIsJoining(false);
    }
  }

  return (
    <>
      <p className="settings-page__hint">{t("household.noHouse.intro")}</p>

      <form className="settings-page__section" onSubmit={handleCreate} noValidate>
        <h2 className="settings-page__section-title">{t("household.noHouse.createTitle")}</h2>
        <HouseNameField value={name} onChange={setName} error={nameErrors.name} />
        {createError && <p className="form-error">{createError}</p>}
        <div className="settings-page__actions">
          <button type="submit" disabled={isCreating}>
            {t("household.noHouse.createButton")}
          </button>
        </div>
      </form>

      <form className="settings-page__section" onSubmit={handleJoin} noValidate>
        <h2 className="settings-page__section-title">{t("household.noHouse.joinTitle")}</h2>
        <label htmlFor="inviteCode">{t("household.noHouse.joinLabel")}</label>
        <input
          id="inviteCode"
          value={inviteCode}
          onChange={(e) => setInviteCode(e.target.value.toUpperCase())}
          autoComplete="off"
        />
        {joinError && <p className="form-error">{joinError}</p>}
        <div className="settings-page__actions">
          <button type="submit" disabled={isJoining || inviteCode.trim().length === 0}>
            {t("household.noHouse.joinButton")}
          </button>
        </div>
      </form>
    </>
  );
}

/** Household details/management shown once the profile belongs to one. */
function HasHousehold({
  house,
  currentUserId,
  onChanged,
}: {
  house: HouseView;
  currentUserId: number | null;
  onChanged: () => void;
}) {
  const { t } = useTranslation();
  const isAdmin = currentUserId !== null && currentUserId === house.adminId;

  const [name, setName] = useState(house.name);
  const [nameErrors, setNameErrors] = useState<Record<string, string>>({});
  const [saveState, setSaveState] = useState<SaveState>("idle");
  const [saveError, setSaveError] = useState<string | null>(null);
  const nameTimeout = useRef<number | undefined>(undefined);

  useEffect(() => {
    setName(house.name);
  }, [house.name]);

  useEffect(() => {
    return () => window.clearTimeout(nameTimeout.current);
  }, []);

  function handleNameChange(newName: string) {
    setName(newName);
    window.clearTimeout(nameTimeout.current);

    const result = renameHouseSchema.safeParse({ name: newName });
    if (!result.success) {
      setNameErrors(fieldErrorsFrom(result.error));
      setSaveState("idle");
      return;
    }
    setNameErrors({});
    setSaveState("saving");
    nameTimeout.current = window.setTimeout(async () => {
      try {
        await apiClient.renameHouse(result.data.name);
        setSaveState("saved");
      } catch (err) {
        const code = err instanceof ApiError ? err.code : ErrorCode.INTERNAL_ERROR;
        setSaveError(errorMessageService.getLabel(code));
        setSaveState("error");
      }
    }, HOUSE_NAME_DEBOUNCE_MS);
  }

  return (
    <>
      <div className="settings-page__section">
        <HouseNameField value={name} onChange={handleNameChange} error={nameErrors.name} />
        {saveState === "saving" && <p className="settings-page__saving">{t("common.saving")}</p>}
        {saveState === "saved" && <p className="settings-page__saved">{t("common.saved")}</p>}
        {saveState === "error" && <p className="field-error">{saveError}</p>}

        <div className="settings-page__invite-section">
          <p className="settings-page__hint">{t("household.inviteCodeLabel")}</p>
          <InviteCode code={house.inviteCode} />
        </div>
      </div>

      <div className="settings-page__section">
        <h2 className="settings-page__section-title">{t("household.membersTitle")}</h2>
        <ul className="settings-page__members">
          {house.members.map((member) => (
            <li key={member.id} className="settings-page__member">
              <span>
                {member.firstName} {member.lastName}
                {member.id === currentUserId && t("household.youSuffix")}
                {member.id === house.adminId && (
                  <span className="settings-page__member-badge">{t("household.adminBadge")}</span>
                )}
              </span>
              {isAdmin && member.id !== currentUserId && (
                <RemoveMemberButton memberId={member.id} onChanged={onChanged} />
              )}
            </li>
          ))}
        </ul>
      </div>

      <SourcesSection />

      {isAdmin ? (
        <DeleteHouseholdSection onChanged={onChanged} />
      ) : (
        <LeaveHouseholdSection onChanged={onChanged} />
      )}
    </>
  );
}

/**
 * Which recipe sources this household sees in its recipe tabs — any member
 * can change this, not just the admin (same posture as renaming, unlike
 * deleting the household or removing a member). Renders nothing while
 * loading or once loaded if no source is implemented yet (`getSources()`
 * empty — currently always the case): no point showing an empty picker in
 * a settings page any more than in the onboarding wizard's own source step.
 */
function SourcesSection() {
  const { t } = useTranslation();

  const [sources, setSources] = useState<SourceView[]>([]);
  const [sourceIds, setSourceIds] = useState<number[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  const [saveState, setSaveState] = useState<SaveState>("idle");
  const [saveError, setSaveError] = useState<string | null>(null);
  const saveTimeout = useRef<number | undefined>(undefined);

  useEffect(() => {
    let cancelled = false;
    Promise.all([apiClient.getSources(), apiClient.getHouseSourceIds()])
      .then(([sourcesResult, sourceIdsResult]) => {
        if (cancelled) return;
        setSources(sourcesResult);
        setSourceIds(sourceIdsResult);
      })
      // Not essential for the rest of the page to render — the sources
      // list just stays empty (nothing to toggle) rather than stranding
      // the visitor over a transient failure, same reasoning as
      // OnboardingSourcesPage's own catch.
      .catch(() => {})
      .finally(() => {
        if (!cancelled) setIsLoading(false);
      });
    return () => {
      cancelled = true;
      window.clearTimeout(saveTimeout.current);
    };
  }, []);

  function handleChange(newSourceIds: number[]) {
    setSourceIds(newSourceIds);
    window.clearTimeout(saveTimeout.current);
    setSaveState("saving");
    saveTimeout.current = window.setTimeout(async () => {
      try {
        await apiClient.updateHouseSourceIds(newSourceIds);
        setSaveState("saved");
      } catch (err) {
        const code = err instanceof ApiError ? err.code : ErrorCode.INTERNAL_ERROR;
        setSaveError(errorMessageService.getLabel(code));
        setSaveState("error");
      }
    }, HOUSE_SOURCES_DEBOUNCE_MS);
  }

  if (isLoading || sources.length === 0) {
    return null;
  }

  return (
    <div className="settings-page__section">
      <h2 className="settings-page__section-title">{t("household.sources.title")}</h2>
      <p className="settings-page__hint">{t("household.sources.hint")}</p>
      <SourceSelect
        legend={t("household.sources.legend")}
        sources={sources}
        value={sourceIds}
        onChange={handleChange}
      />
      {saveState === "saving" && <p className="settings-page__saving">{t("common.saving")}</p>}
      {saveState === "saved" && <p className="settings-page__saved">{t("common.saved")}</p>}
      {saveState === "error" && <p className="field-error">{saveError}</p>}
    </div>
  );
}

/** Read-only invite code display with a one-click clipboard copy. */
function InviteCode({ code }: { code: string }) {
  const { t } = useTranslation();
  const [copied, setCopied] = useState(false);

  async function handleCopy() {
    try {
      await navigator.clipboard.writeText(code);
      setCopied(true);
      window.setTimeout(() => setCopied(false), 2000);
    } catch (err) {
      // Rethrown as-is — nothing meaningful to show inline for a clipboard
      // failure here (no error state on this small button); this handler
      // just isn't allowed a bare `await` per the repo's async/try-catch
      // convention.
      throw err;
    }
  }

  return (
    <div className="settings-page__actions">
      <span className="settings-page__invite-code">{code}</span>
      <button type="button" onClick={handleCopy}>
        {copied ? t("household.copied") : t("household.copyButton")}
      </button>
    </div>
  );
}

/** Admin-only button removing one specific member from the household. */
function RemoveMemberButton({ memberId, onChanged }: { memberId: number; onChanged: () => void }) {
  const { t } = useTranslation();
  const [isRemoving, setIsRemoving] = useState(false);

  async function handleRemove() {
    setIsRemoving(true);
    try {
      await apiClient.removeHouseMember(memberId);
      onChanged();
    } catch (err) {
      throw err; // see InviteCode's handleCopy() catch comment above
    } finally {
      setIsRemoving(false);
    }
  }

  return (
    <button type="button" onClick={handleRemove} disabled={isRemoving}>
      {t("household.removeButton")}
    </button>
  );
}

/** Admin-only danger zone: delete the household outright, with an inline two-step confirmation. */
function DeleteHouseholdSection({ onChanged }: { onChanged: () => void }) {
  const { t } = useTranslation();
  const [isConfirming, setIsConfirming] = useState(false);
  const [isDeleting, setIsDeleting] = useState(false);

  async function handleDelete() {
    setIsDeleting(true);
    try {
      await apiClient.deleteHouse();
      onChanged();
    } catch (err) {
      throw err; // see InviteCode's handleCopy() catch comment above
    } finally {
      setIsDeleting(false);
    }
  }

  return (
    <div className="settings-page__section settings-page__danger-zone">
      <h2 className="settings-page__section-title">{t("household.dangerZone.title")}</h2>
      <p className="settings-page__hint">{t("household.dangerZone.description")}</p>
      <div className="settings-page__actions">
        {!isConfirming ? (
          <button
            type="button"
            className="settings-page__danger-button"
            onClick={() => setIsConfirming(true)}
          >
            {t("household.dangerZone.deleteButton")}
          </button>
        ) : (
          <>
            <button
              type="button"
              className="settings-page__danger-button"
              onClick={handleDelete}
              disabled={isDeleting}
            >
              {t("household.dangerZone.confirmButton")}
            </button>
            <button type="button" onClick={() => setIsConfirming(false)} disabled={isDeleting}>
              {t("household.dangerZone.cancelButton")}
            </button>
          </>
        )}
      </div>
    </div>
  );
}

/** Non-admin members' way out: leave the household (immediate — no confirmation step, unlike deleting it entirely, since it only affects the leaving member). */
function LeaveHouseholdSection({ onChanged }: { onChanged: () => void }) {
  const { t } = useTranslation();
  const [isLeaving, setIsLeaving] = useState(false);

  async function handleLeave() {
    setIsLeaving(true);
    try {
      await apiClient.leaveHouse();
      onChanged();
    } catch (err) {
      throw err; // see InviteCode's handleCopy() catch comment above
    } finally {
      setIsLeaving(false);
    }
  }

  return (
    <div className="settings-page__section">
      <div className="settings-page__actions">
        <button type="button" onClick={handleLeave} disabled={isLeaving}>
          {t("household.leaveButton")}
        </button>
      </div>
    </div>
  );
}
