import type { DecisionApproachDetailEntry } from "../../app/(app)/create/types";
import decisionApproachesMessages from "../../messages/en/create/customRule/decisionApproaches.json";

export type DecisionApproachKeyResourceItem = {
  id: string;
  label: string;
};

function uniquePreserveOrder(values: readonly string[]): string[] {
  const seen = new Set<string>();
  const out: string[] = [];
  for (const value of values) {
    if (seen.has(value)) continue;
    seen.add(value);
    out.push(value);
  }
  return out;
}

function isKeyResourceItem(value: unknown): value is DecisionApproachKeyResourceItem {
  if (!value || typeof value !== "object") return false;
  const row = value as { id?: unknown; label?: unknown };
  return typeof row.id === "string" && typeof row.label === "string";
}

/**
 * Sidebar “key resource” checkboxes on the decision-approaches step.
 * These labels are also offered as Applicable Scope chips on every approach.
 */
export function decisionApproachKeyResourceItems(): DecisionApproachKeyResourceItem[] {
  const items = (
    decisionApproachesMessages as {
      messageBox?: { items?: unknown };
    }
  ).messageBox?.items;
  if (!Array.isArray(items)) return [];
  return items.filter(isKeyResourceItem);
}

export function decisionApproachKeyResourceLabels(): string[] {
  return decisionApproachKeyResourceItems().map((item) => item.label);
}

export function selectedKeyResourceLabelsFromCheckedIds(
  checkedIds: readonly string[],
): string[] {
  const idSet = new Set(checkedIds);
  return decisionApproachKeyResourceItems()
    .filter((item) => idSet.has(item.id))
    .map((item) => item.label);
}

export function decisionApproachKeyResourceIdsFromLabels(
  labels: readonly string[],
): string[] {
  const labelSet = new Set(labels);
  return decisionApproachKeyResourceItems()
    .filter((item) => labelSet.has(item.label))
    .map((item) => item.id);
}

/**
 * Sidebar checkboxes follow the open approach while its modal is up.
 * With no modal they show key resources assigned to any selected approach,
 * plus extra reminder checks that are not painted onto chips.
 */
export function decisionApproachKeyResourceCheckboxIds(params: {
  detailsById: Record<string, DecisionApproachDetailEntry> | undefined;
  selectedApproachIds: readonly string[];
  reminderIds?: readonly string[];
  openDraft?: DecisionApproachDetailEntry | null;
}): string[] {
  if (params.openDraft) {
    return decisionApproachKeyResourceIdsFromLabels(
      params.openDraft.selectedApplicableScope,
    );
  }
  const assignedLabels: string[] = [];
  for (const id of params.selectedApproachIds) {
    const entry = params.detailsById?.[id];
    if (!entry) continue;
    assignedLabels.push(...entry.selectedApplicableScope);
  }
  return uniquePreserveOrder([
    ...decisionApproachKeyResourceIdsFromLabels(assignedLabels),
    ...(params.reminderIds ?? []),
  ]);
}

export function stringArraysEqual(
  a: readonly string[],
  b: readonly string[],
): boolean {
  if (a.length !== b.length) return false;
  return a.every((value, index) => value === b[index]);
}

/**
 * Offer the four sidebar labels as Applicable Scope chips without persisting
 * them onto `applicableScope` (unchecked keys must not publish as defaults).
 */
export function withDecisionApproachKeyResourceScopes(
  scopes: readonly string[],
): string[] {
  return uniquePreserveOrder([
    ...scopes,
    ...decisionApproachKeyResourceLabels(),
  ]);
}

/**
 * Keep non-key chip selections, then select the key-resource labels whose
 * sidebar boxes are checked.
 */
export function applyDecisionApproachKeyResources(
  entry: DecisionApproachDetailEntry,
  selectedKeyResourceLabels: readonly string[],
): DecisionApproachDetailEntry {
  const keyLabels = decisionApproachKeyResourceLabels();
  const keySet = new Set(keyLabels);
  const selectedSet = new Set(selectedKeyResourceLabels);
  return {
    ...entry,
    selectedApplicableScope: uniquePreserveOrder([
      ...entry.selectedApplicableScope.filter((scope) => !keySet.has(scope)),
      ...keyLabels.filter((label) => selectedSet.has(label)),
    ]),
  };
}

export function withoutDecisionApproachKeyResourceLabels(
  scopes: readonly string[],
): string[] {
  const keySet = new Set(decisionApproachKeyResourceLabels());
  return scopes.filter((scope) => !keySet.has(scope));
}

/**
 * Publish selected original scopes (or all originals when none are picked)
 * plus any checked key-resource labels. Checking a key resource must not drop
 * unselected default scopes.
 */
export function decisionApproachScopeForPublish(
  applicableScope: readonly unknown[],
  selectedApplicableScope: readonly unknown[],
): string[] {
  const asStrings = (values: readonly unknown[]): string[] =>
    values.filter((value): value is string => typeof value === "string");
  const keySet = new Set(decisionApproachKeyResourceLabels());
  const original = withoutDecisionApproachKeyResourceLabels(
    asStrings(applicableScope),
  );
  const selected = asStrings(selectedApplicableScope);
  const selectedOther = withoutDecisionApproachKeyResourceLabels(selected);
  const selectedKeys = selected.filter((scope) => keySet.has(scope));
  return uniquePreserveOrder([
    ...(selectedOther.length > 0 ? selectedOther : original),
    ...selectedKeys,
  ]);
}

export function syncDecisionApproachKeyResourceDetails(
  detailsById: Record<string, DecisionApproachDetailEntry> | undefined,
  extraIds: readonly string[],
  selectedKeyResourceLabels: readonly string[],
  seed: (_id: string) => DecisionApproachDetailEntry,
): Record<string, DecisionApproachDetailEntry> {
  const next: Record<string, DecisionApproachDetailEntry> = {};
  const ids = new Set<string>([...Object.keys(detailsById ?? {}), ...extraIds]);
  for (const id of ids) {
    next[id] = applyDecisionApproachKeyResources(
      detailsById?.[id] ?? seed(id),
      selectedKeyResourceLabels,
    );
  }
  return next;
}
