import type {
  CreateFlowMethodCardFacetSection,
  CreateFlowState,
} from "../../app/(app)/create/types";
import {
  CUSTOM_RULE_FACETS,
  PUBLISHED_CUSTOM_RULE_SELECTION_KEYS,
} from "./customRuleFacets";
import type { PublishedMethodSelections } from "./buildPublishPayload";
import type { StoredLastPublishedRule } from "./lastPublishedRule";
import { normalizePublishedDocumentForEdit } from "./normalizePublishedDocumentForEdit";
import { methodLabelFor } from "./finalReviewChipPresets";
import type { TemplateFacetGroupKey } from "./templateReviewMapping";

function customMethodCardMetaFromPublishedSelections(
  ms: PublishedMethodSelections,
): CreateFlowState["customMethodCardMetaById"] | undefined {
  const meta: NonNullable<CreateFlowState["customMethodCardMetaById"]> = {};
  const absorb = (
    groupKey: TemplateFacetGroupKey,
    rows:
      | Array<{
          id: string;
          label: string;
          supportText?: string;
        }>
      | undefined,
  ) => {
    if (!rows) return;
    for (const row of rows) {
      const id = typeof row.id === "string" ? row.id.trim() : "";
      if (!id) continue;
      if (methodLabelFor(groupKey, id).length > 0) continue;
      const label = typeof row.label === "string" ? row.label.trim() : "";
      if (!label) continue;
      const supportText =
        typeof row.supportText === "string" ? row.supportText : "";
      meta[id] = { label, supportText };
    }
  };
  absorb("communication", ms.communication);
  absorb("membership", ms.membership);
  absorb("decisionApproaches", ms.decisionApproaches);
  absorb("conflictManagement", ms.conflictManagement);
  return Object.keys(meta).length > 0 ? meta : undefined;
}

/**
 * True when `patch` (from {@link createFlowStateFromPublishedRule}) expects
 * non-empty facet selections but `state` still has none for that facet.
 *
 * Used so `/create/edit-rule` hydration is not skipped after TopNav **Edit**
 * pre-clears `sections` (which made `sections?.length === 0` look like a
 * finished hydrate even though method ids were never merged).
 */
export function isPublishedRuleSelectionMissing(
  state: CreateFlowState,
  patch: Partial<CreateFlowState>,
): boolean {
  for (const k of PUBLISHED_CUSTOM_RULE_SELECTION_KEYS) {
    const desired = patch[k];
    if (!Array.isArray(desired) || desired.length === 0) continue;
    const actualRaw = state[k];
    const actual = Array.isArray(actualRaw) ? actualRaw : [];
    if (actual.length === 0) return true;
  }
  return false;
}

/**
 * True when published-rule hydration should run (or continue) — facet ids still
 * empty, or {@link createFlowStateFromPublishedRule} produced
 * `customMethodCardMetaById` for user-authored method UUIDs that `state` does
 * not have yet (final-review chips use meta + id when no preset label exists).
 */
export function isPublishedRuleHydratePatchIncomplete(
  state: CreateFlowState,
  patch: Partial<CreateFlowState>,
): boolean {
  if (isPublishedRuleSelectionMissing(state, patch)) return true;
  const pm = patch.customMethodCardMetaById;
  if (!pm || Object.keys(pm).length === 0) return false;
  const sm = state.customMethodCardMetaById ?? {};
  for (const key of Object.keys(pm)) {
    if (!sm[key]) return true;
  }
  return false;
}

/**
 * Pin flags for method-card facets: persisted for hydration and footer Confirm.
 * Card Stack display pulls selections to the top whenever `selected*` ids are
 * non-empty (`useMethodCardDeckOrdering`); this map still merges on edit /
 * template paths so drafts mirror post-Confirm expectations.
 *
 * Caller should spread onto existing `methodSectionsPinCommitted` so unrelated facets stay
 * as-is (`{ ...prior, ...this }`).
 */
export function methodSectionsPinsForHydratedSelections(
  patch: Partial<CreateFlowState>,
): Partial<Record<CreateFlowMethodCardFacetSection, boolean>> {
  const out: Partial<Record<CreateFlowMethodCardFacetSection, boolean>> = {};
  for (const row of CUSTOM_RULE_FACETS) {
    if (row.kind !== "method" || row.apiMethodSectionId == null) continue;
    const sel = patch[row.selectedIdsStateKey];
    if (Array.isArray(sel) && sel.length > 0) {
      out[row.apiMethodSectionId] = true;
    }
  }
  return out;
}

/** @see {@link methodSectionsPinsForHydratedSelections} — published-rule hydrate naming. */
export function methodSectionsPinsFromPublishedHydratePatch(
  patch: Partial<CreateFlowState>,
): Partial<Record<CreateFlowMethodCardFacetSection, boolean>> {
  return methodSectionsPinsForHydratedSelections(patch);
}

/**
 * Rehydrate create-flow fields from a stored published rule so `/create/edit-rule`
 * can render final-review editors after refresh or when branching from completed.
 */
export function createFlowStateFromPublishedRule(
  rule: StoredLastPublishedRule,
): Partial<CreateFlowState> {
  const doc = normalizePublishedDocumentForEdit(
    rule.document,
  ) as StoredLastPublishedRule["document"];
  const out: Partial<CreateFlowState> = {
    title: rule.title,
    editingPublishedRuleId: rule.id,
  };
  const sum = typeof rule.summary === "string" ? rule.summary.trim() : "";
  if (sum.length > 0) {
    out.communityContext = sum;
    out.summary = sum;
  }

  const coreValues = doc.coreValues;
  if (Array.isArray(coreValues) && coreValues.length > 0) {
    const selectedCoreValueIds: string[] = [];
    const coreValuesChipsSnapshot: NonNullable<
      CreateFlowState["coreValuesChipsSnapshot"]
    > = [];
    const coreValueDetailsByChipId: NonNullable<
      CreateFlowState["coreValueDetailsByChipId"]
    > = {};

    for (const row of coreValues) {
      if (!row || typeof row !== "object") continue;
      const o = row as Record<string, unknown>;
      const chipIdRaw = typeof o.chipId === "string" ? o.chipId.trim() : "";
      const label = typeof o.label === "string" ? o.label.trim() : "";
      if (!label) continue;
      const chipId =
        chipIdRaw.length > 0 ? chipIdRaw : `hydrated-${label.toLowerCase()}`;
      selectedCoreValueIds.push(chipId);
      coreValuesChipsSnapshot.push({
        id: chipId,
        label,
        state: "selected",
      });
      coreValueDetailsByChipId[chipId] = {
        meaning: typeof o.meaning === "string" ? o.meaning : "",
        signals: typeof o.signals === "string" ? o.signals : "",
      };
    }
    out.selectedCoreValueIds = selectedCoreValueIds;
    out.coreValuesChipsSnapshot = coreValuesChipsSnapshot;
    out.coreValueDetailsByChipId = coreValueDetailsByChipId;
  }

  const avatarUrl =
    typeof doc.communityAvatarUrl === "string"
      ? doc.communityAvatarUrl.trim()
      : "";
  if (avatarUrl.length > 0) {
    out.communityAvatarUrl = avatarUrl;
  }

  const blocksRaw = doc.customMethodCardFieldBlocksById;
  if (
    blocksRaw &&
    typeof blocksRaw === "object" &&
    !Array.isArray(blocksRaw)
  ) {
    out.customMethodCardFieldBlocksById =
      blocksRaw as NonNullable<CreateFlowState["customMethodCardFieldBlocksById"]>;
  }

  const msRaw = doc.methodSelections;
  if (!msRaw || typeof msRaw !== "object" || Array.isArray(msRaw)) {
    out.sections = [];
    return out;
  }
  const ms = msRaw as PublishedMethodSelections;

  if (Array.isArray(ms.communication) && ms.communication.length > 0) {
    out.selectedCommunicationMethodIds = ms.communication.map((x) => x.id);
    out.communicationMethodDetailsById = Object.fromEntries(
      ms.communication.map((x) => [x.id, x.sections]),
    );
  }
  if (Array.isArray(ms.membership) && ms.membership.length > 0) {
    out.selectedMembershipMethodIds = ms.membership.map((x) => x.id);
    out.membershipMethodDetailsById = Object.fromEntries(
      ms.membership.map((x) => [x.id, x.sections]),
    );
  }
  if (
    Array.isArray(ms.decisionApproaches) &&
    ms.decisionApproaches.length > 0
  ) {
    out.selectedDecisionApproachIds = ms.decisionApproaches.map((x) => x.id);
    out.decisionApproachDetailsById = Object.fromEntries(
      ms.decisionApproaches.map((x) => [x.id, x.sections]),
    );
  }
  if (
    Array.isArray(ms.conflictManagement) &&
    ms.conflictManagement.length > 0
  ) {
    out.selectedConflictManagementIds = ms.conflictManagement.map(
      (x) => x.id,
    );
    out.conflictManagementDetailsById = Object.fromEntries(
      ms.conflictManagement.map((x) => [x.id, x.sections]),
    );
  }

  const customMeta = customMethodCardMetaFromPublishedSelections(ms);
  if (customMeta) {
    out.customMethodCardMetaById = customMeta;
  }

  /** Drop template `sections` so final-review uses `methodSelections` / selected ids (edit path). */
  out.sections = [];
  return out;
}
