import type {
  PredictionCard,
  PredictionDraftValue,
  PredictionEntry,
  UpsertPredictionPayload,
} from "@/types/predictions";

function roundWithStep(value: number, step: number) {
  if (step <= 0) {
    return value;
  }
  return Math.round(value / step) * step;
}

function getExpectedNumberStep(card: PredictionCard, fieldStep?: number | null) {
  if (card.code === "legacy_weight") {
    return 0.005;
  }

  return fieldStep ?? null;
}

export function getDefaultDraftValues(card: PredictionCard): PredictionDraftValue[] {
  return card.fields.map((field) => ({
    fieldId: field.id,
    valueText: "",
    valueNumber: null,
    valueDate: "",
  }));
}

export function getDraftValuesFromEntry(
  card: PredictionCard,
  entry?: PredictionEntry,
): PredictionDraftValue[] {
  const defaults = getDefaultDraftValues(card);

  if (!entry) {
    return defaults;
  }

  const valuesByField = new Map(entry.values.map((value) => [value.fieldId, value]));

  return defaults.map((draft) => {
    const value = valuesByField.get(draft.fieldId);
    return {
      fieldId: draft.fieldId,
      valueText: value?.valueText ?? "",
      valueNumber: value?.valueNumber ?? null,
      valueDate: value?.valueDate ? value.valueDate.slice(0, 10) : "",
    };
  });
}

export function validateDraft(card: PredictionCard, draftValues: PredictionDraftValue[]) {
  const byField = new Map(draftValues.map((value) => [value.fieldId, value]));

  for (const field of card.fields) {
    const draft = byField.get(field.id);
    if (!draft) {
      return { valid: false, message: `Le champ ${field.label} est manquant` };
    }

    if (!field.isRequired) {
      continue;
    }

    if (card.valueType === "NUMBER") {
      if (draft.valueNumber == null) {
        return { valid: false, message: `Le champ ${field.label} est obligatoire` };
      }

      if (field.minNumber != null && draft.valueNumber < field.minNumber) {
        return { valid: false, message: `${field.label} doit etre >= ${field.minNumber}` };
      }

      if (field.maxNumber != null && draft.valueNumber > field.maxNumber) {
        return { valid: false, message: `${field.label} doit etre <= ${field.maxNumber}` };
      }

      const expectedStep = getExpectedNumberStep(card, field.stepNumber);

      if (expectedStep != null && expectedStep > 0) {
        const rounded = roundWithStep(draft.valueNumber, expectedStep);
        const diff = Math.abs(rounded - draft.valueNumber);
        if (diff > 0.000001) {
          return {
            valid: false,
            message: `${field.label} doit respecter un pas de ${expectedStep}`,
          };
        }
      }
    }

    if (
      (card.valueType === "TEXT" ||
        card.valueType === "SELECT" ||
        card.valueType === "MULTI_TEXT") &&
      draft.valueText.trim().length === 0
    ) {
      return { valid: false, message: `Le champ ${field.label} est obligatoire` };
    }

    if (card.valueType === "DATE" && draft.valueDate.trim().length === 0) {
      return { valid: false, message: `Le champ ${field.label} est obligatoire` };
    }
  }

  return { valid: true as const };
}

export function serializeDraftToUpsertPayload(
  card: PredictionCard,
  draftValues: PredictionDraftValue[],
): UpsertPredictionPayload {
  const values = draftValues
    .map((draft) => {
      if (card.valueType === "NUMBER") {
        if (draft.valueNumber == null) {
          return null;
        }
        return { fieldId: draft.fieldId, valueNumber: draft.valueNumber };
      }

      if (card.valueType === "DATE") {
        if (!draft.valueDate) {
          return null;
        }
        return { fieldId: draft.fieldId, valueDate: draft.valueDate };
      }

      const text = draft.valueText.trim();
      if (!text) {
        return null;
      }
      return { fieldId: draft.fieldId, valueText: text };
    })
    .filter((value): value is NonNullable<typeof value> => value != null);

  return { values };
}

export function serializeChangedDraftToUpsertPayload(
  card: PredictionCard,
  draftValues: PredictionDraftValue[],
  persistedValues: PredictionDraftValue[],
): UpsertPredictionPayload {
  const persistedByField = new Map(persistedValues.map((value) => [value.fieldId, value]));

  const changedDraftValues = draftValues.filter((draft) => {
    const persisted = persistedByField.get(draft.fieldId);
    if (!persisted) {
      return true;
    }

    return (
      persisted.valueText.trim() !== draft.valueText.trim() ||
      persisted.valueNumber !== draft.valueNumber ||
      persisted.valueDate !== draft.valueDate
    );
  });

  const values = changedDraftValues
    .map((draft) => {
      if (card.valueType === "NUMBER") {
        if (draft.valueNumber == null) {
          return null;
        }
        return { fieldId: draft.fieldId, valueNumber: draft.valueNumber };
      }

      if (card.valueType === "DATE") {
        if (!draft.valueDate) {
          return null;
        }
        return { fieldId: draft.fieldId, valueDate: draft.valueDate };
      }

      const text = draft.valueText.trim();
      if (!text) {
        return { fieldId: draft.fieldId, valueText: "" };
      }

      return { fieldId: draft.fieldId, valueText: text };
    })
    .filter((value): value is NonNullable<typeof value> => value != null);

  return { values };
}

export function areDraftValuesEqual(a: PredictionDraftValue[], b: PredictionDraftValue[]) {
  if (a.length !== b.length) {
    return false;
  }

  const normalize = (values: PredictionDraftValue[]) =>
    [...values]
      .sort((left, right) => left.fieldId.localeCompare(right.fieldId))
      .map((value) => ({
        fieldId: value.fieldId,
        valueText: value.valueText.trim(),
        valueNumber: value.valueNumber,
        valueDate: value.valueDate,
      }));

  return JSON.stringify(normalize(a)) === JSON.stringify(normalize(b));
}
