"use client";

import type { AssetProfile, Token } from "@sdp/types";
import { useRouter } from "next/navigation";
import { useEffect, useMemo, useState } from "react";
import { toast } from "sonner";
import { useTranslations } from "@/i18n/provider";
import { buildIssuanceMetadata, getAssetDetailsErrors } from "../../create/draft-mapping";
import type { DraftState } from "../../create/issuance-draft-wizard.types";
import {
  isMaxSupplyBelowMintedSupply,
  isSupplyLockedOnChain,
} from "../token-management-workspace.utils";
import { updateAssetProfileAction } from "./actions";
import { areDraftsEquivalent, profileToDraftState } from "./asset-profile-mapping";

/**
 * Edit-in-place form state for the asset management workspace: one draft
 * spanning the Details and Compliance tabs, hydrated from the profile + token,
 * saved as a whole through the save bar. No localStorage — the wizard's
 * persisted create-draft must stay untouched.
 */
export function useAssetProfileForm({
  token,
  assetProfile: initialAssetProfile,
}: {
  token: Token;
  assetProfile: AssetProfile;
}) {
  const t = useTranslations();
  const router = useRouter();
  // The save action returns the updated profile; keep the freshest copy so the
  // baseline re-derives without waiting for a server re-render.
  const [assetProfile, setAssetProfile] = useState(initialAssetProfile);
  useEffect(() => {
    if (initialAssetProfile.updatedAt > assetProfile.updatedAt) {
      setAssetProfile(initialAssetProfile);
    }
  }, [initialAssetProfile, assetProfile.updatedAt]);

  const baseline = useMemo(() => profileToDraftState(assetProfile, token), [assetProfile, token]);
  const [draft, setDraft] = useState<DraftState>(baseline);
  const [baselineKey, setBaselineKey] = useState(assetProfile.updatedAt);
  // Re-hydrate the form when the underlying profile changes (post-save or after
  // a router.refresh picked up someone else's update) — but never mid-edit.
  const dirty = !areDraftsEquivalent(draft, baseline);
  useEffect(() => {
    if (assetProfile.updatedAt !== baselineKey && !dirty) {
      setDraft(baseline);
      setBaselineKey(assetProfile.updatedAt);
    }
  }, [assetProfile.updatedAt, baselineKey, baseline, dirty]);

  const [saving, setSaving] = useState(false);
  const [showErrors, setShowErrors] = useState(false);

  const updateDraft = (patch: Partial<DraftState>) => {
    if (saving) {
      return;
    }
    setDraft((previous) => ({ ...previous, ...patch }));
  };

  // The cap is frozen once the mint authority is revoked; until then it stays
  // editable, and this decides both the field's mode and whether save sends it.
  const supplyLocked = isSupplyLockedOnChain(token);

  const errors = getAssetDetailsErrors(draft, t);
  if (!draft.name.trim()) {
    errors.name = t("DashboardIssuance.errors.assetNameRequired");
  }
  // Unlike the creation wizard, this form knows how much is already minted — a
  // cap under that could never be satisfied, so catch it before the round-trip.
  if (
    !supplyLocked &&
    !errors.maxSupply &&
    draft.maxSupply.trim() &&
    isMaxSupplyBelowMintedSupply(draft.maxSupply, token.totalSupply)
  ) {
    errors.maxSupply = t("DashboardIssuance.errors.maxSupplyBelowMinted", {
      amount: token.totalSupply,
      symbol: token.symbol,
    });
  }
  const errorCount = Object.keys(errors).length;

  const discard = () => {
    setDraft(baseline);
    setShowErrors(false);
  };

  const save = async () => {
    if (!dirty || saving) {
      return;
    }
    if (errorCount > 0) {
      setShowErrors(true);
      toast.error(t("DashboardIssuance.assetProfileForm.fixHighlightedFields"));
      return;
    }

    setSaving(true);
    try {
      const isDeployed = Boolean(token.mintAddress);
      const result = await updateAssetProfileAction({
        tokenId: token.id,
        profileId: assetProfile.id,
        rebuiltMetadata: buildIssuanceMetadata(draft),
        tokenPatch: {
          name: draft.name.trim(),
          description: draft.description.trim() || null,
          uri: draft.metadataUri.trim() || null,
          imageUrl: draft.imageUrl.trim() || null,
          // Symbol, decimals, and access-control are baked into the mint at
          // deploy — editable only while undeployed; the API rejects them after.
          ...(isDeployed
            ? {}
            : {
                symbol: draft.symbol.trim(),
                decimals: Number(draft.decimals),
                requiresAllowlist: draft.accessControl === "allowlist",
              }),
          // Blank means uncapped. Withheld once the supply is locked on-chain:
          // the field is read-only then, and the API rejects it.
          ...(supplyLocked ? {} : { maxSupply: draft.maxSupply.trim() || null }),
        },
      });

      if (result.state === "error") {
        toast.error(result.message);
        return;
      }

      toast.success(result.message);
      setShowErrors(false);
      if (result.assetProfile) {
        setAssetProfile(result.assetProfile);
        setBaselineKey(result.assetProfile.updatedAt);
        setDraft(
          profileToDraftState(result.assetProfile, {
            ...token,
            ...draftTokenPatch(draft),
          })
        );
      }
      router.refresh();
    } finally {
      setSaving(false);
    }
  };

  // Warn before the tab/window closes with unsaved changes. (App Router has no
  // client-side navigation blocker; internal nav away discards silently.)
  useEffect(() => {
    if (!dirty) {
      return;
    }
    const handler = (event: BeforeUnloadEvent) => {
      event.preventDefault();
    };
    window.addEventListener("beforeunload", handler);
    return () => window.removeEventListener("beforeunload", handler);
  }, [dirty]);

  return {
    draft,
    updateDraft,
    dirty,
    saving,
    errors,
    errorCount,
    showErrors,
    save,
    discard,
    assetProfile,
    supplyLocked,
  };
}

// The token fields the save action just wrote — applied optimistically so the
// re-derived baseline matches the saved draft before router.refresh lands.
// Read-only fields are included too: the form can't have changed them, so they
// carry the token's own value.
function draftTokenPatch(draft: DraftState): Partial<Token> {
  return {
    name: draft.name.trim(),
    symbol: draft.symbol.trim(),
    decimals: Number(draft.decimals),
    description: draft.description.trim() || null,
    uri: draft.metadataUri.trim() || null,
    imageUrl: draft.imageUrl.trim() || null,
    maxSupply: draft.maxSupply.trim() || null,
  };
}

export type AssetProfileForm = ReturnType<typeof useAssetProfileForm>;
