/**
 * Mapping signature utilities
 *
 * When the user navigates back to Match Columns and forward again, we need to know
 * *what* about the mapping actually changed so the Validation store can:
 *   - preserve edits for fields whose source did not change,
 *   - overwrite only the fields whose source did change,
 *   - re-run UPC validation / item-number generation only when the supplier or the
 *     UPC column mapping changed.
 *
 * A "signature" is a compact, comparable description of how every field is sourced.
 */

import { ColumnType, type Columns, type GlobalSelections } from '../../MatchColumnsStep/types';

export interface MappingSignature {
  /** field key -> a string describing where that field's value comes from */
  perField: Record<string, string>;
  /** resolved supplier (global selection), surfaced for UPC re-run gating */
  supplier: string;
}

/**
 * Encode a single column's contribution to the signature.
 * matchedOptions are folded in so re-mapping a select value counts as a change.
 */
const encodeColumn = (column: Columns<string>[number], costIsTotalCost?: boolean): string => {
  let sig = `col:${column.index}:${column.type}`;

  if ('matchedOptions' in column && Array.isArray(column.matchedOptions)) {
    const opts = column.matchedOptions
      .map((o) => `${o.entry ?? ''}=>${o.value ?? ''}`)
      .join('|');
    sig += `:opts(${opts})`;
  }

  // cost_each is post-processed by the "divide by min qty" flag, so the resulting
  // value differs even when the source column is identical — fold the flag in.
  if ('value' in column && column.value === 'cost_each' && costIsTotalCost) {
    sig += ':total';
  }

  return sig;
};

/**
 * Build a signature from the current columns + global selections.
 */
export const computeMappingSignature = (
  columns: Columns<string>,
  globalSelections?: GlobalSelections,
): MappingSignature => {
  const perField: Record<string, string> = {};

  for (const column of columns) {
    if (
      column.type === ColumnType.empty ||
      column.type === ColumnType.ignored ||
      column.type === ColumnType.aiSupplemental
    ) {
      continue;
    }
    if ('value' in column) {
      perField[column.value] = encodeColumn(column, globalSelections?.costIsTotalCost);
    }
  }

  // Global selections act as the source for these fields when not column-mapped.
  const globalKeys: (keyof GlobalSelections)[] = ['supplier', 'company', 'line', 'subline'];
  for (const key of globalKeys) {
    const val = globalSelections?.[key];
    if (val && !perField[key]) {
      perField[key] = `global:${String(val)}`;
    }
  }

  return {
    perField,
    supplier: String(globalSelections?.supplier ?? ''),
  };
};

export interface MappingDiff {
  /** true when nothing relevant changed (safe to skip re-init entirely) */
  equal: boolean;
  /** fields whose source changed (or were newly mapped) — values should be re-pulled */
  changedFieldKeys: string[];
  /** fields that were mapped before but are now unmapped — values should be cleared */
  clearedFieldKeys: string[];
  /** whether UPC validation / item-number generation needs to re-run */
  upcAffected: boolean;
}

const UPC_INPUT_FIELDS = ['supplier', 'upc', 'barcode'];

/**
 * Compare two signatures and describe what changed.
 */
export const diffMappingSignatures = (
  prev: MappingSignature | null | undefined,
  next: MappingSignature,
): MappingDiff => {
  // No previous signature => treat everything as changed (first-time init handles this
  // path separately, but be safe).
  if (!prev) {
    return {
      equal: false,
      changedFieldKeys: Object.keys(next.perField),
      clearedFieldKeys: [],
      upcAffected: true,
    };
  }

  const changedFieldKeys: string[] = [];
  const clearedFieldKeys: string[] = [];

  const allKeys = new Set([...Object.keys(prev.perField), ...Object.keys(next.perField)]);
  for (const key of allKeys) {
    const before = prev.perField[key];
    const after = next.perField[key];
    if (before === after) continue;
    if (after === undefined) {
      clearedFieldKeys.push(key);
    } else {
      changedFieldKeys.push(key);
    }
  }

  const upcAffected = [...changedFieldKeys, ...clearedFieldKeys].some((k) =>
    UPC_INPUT_FIELDS.includes(k),
  );

  return {
    equal: changedFieldKeys.length === 0 && clearedFieldKeys.length === 0,
    changedFieldKeys,
    clearedFieldKeys,
    upcAffected,
  };
};
