import type {
  AdjustmentStatus,
  SensorPlacementScheme,
  SensorPoint,
  SensorPointRow,
} from "./types";

export interface SchemeEditorSnapshot {
  points: SensorPoint[];
  statuses: Record<string, AdjustmentStatus>;
}

export interface SchemeEditorState extends SchemeEditorSnapshot {
  baseline: SensorPoint[];
  history: SchemeEditorSnapshot[];
  future: SchemeEditorSnapshot[];
}

const clonePoints = (points: SensorPoint[]) => points.map((point) => ({ ...point }));

const currentStatuses = (points: SensorPoint[]) =>
  Object.fromEntries(points.map((point) => [point.node_id, "current"])) as Record<
    string,
    AdjustmentStatus
  >;

export const createSchemeEditorState = (
  scheme: SensorPlacementScheme,
): SchemeEditorState => ({
  baseline: clonePoints(scheme.sensor_points),
  points: clonePoints(scheme.sensor_points),
  statuses: currentStatuses(scheme.sensor_points),
  history: [],
  future: [],
});

const snapshot = (state: SchemeEditorState): SchemeEditorSnapshot => ({
  points: clonePoints(state.points),
  statuses: { ...state.statuses },
});

const withHistory = (
  state: SchemeEditorState,
  points: SensorPoint[],
  statuses: Record<string, AdjustmentStatus>,
): SchemeEditorState => ({
  ...state,
  points,
  statuses,
  history: [...state.history, snapshot(state)],
  future: [],
});

export const addSensorPoint = (
  state: SchemeEditorState,
  point: SensorPoint,
): SchemeEditorState => {
  if (state.points.some((item) => item.node_id === point.node_id)) return state;
  return withHistory(
    state,
    [...state.points, { ...point }],
    { ...state.statuses, [point.node_id]: "added" },
  );
};

export const replaceSensorPoint = (
  state: SchemeEditorState,
  sourceNodeId: string,
  point: SensorPoint,
): SchemeEditorState => {
  const sourceIndex = state.points.findIndex(
    (item) => item.node_id === sourceNodeId,
  );
  if (sourceIndex < 0) return state;
  if (
    sourceNodeId !== point.node_id &&
    state.points.some((item) => item.node_id === point.node_id)
  ) {
    return state;
  }

  const nextPoints = clonePoints(state.points);
  nextPoints[sourceIndex] = { ...point };
  const nextStatuses = { ...state.statuses };
  const sourceStatus = nextStatuses[sourceNodeId] ?? "current";
  delete nextStatuses[sourceNodeId];
  const baselineIds = new Set(state.baseline.map((item) => item.node_id));
  nextStatuses[point.node_id] =
    sourceStatus === "added"
      ? "added"
      : baselineIds.has(point.node_id)
        ? "original"
        : "replaced";
  return withHistory(state, nextPoints, nextStatuses);
};

export const deleteSensorPoint = (
  state: SchemeEditorState,
  nodeId: string,
): SchemeEditorState => {
  if (state.points.length <= 1) return state;
  if (!state.points.some((item) => item.node_id === nodeId)) return state;
  const nextStatuses = { ...state.statuses };
  delete nextStatuses[nodeId];
  return withHistory(
    state,
    state.points.filter((item) => item.node_id !== nodeId),
    nextStatuses,
  );
};

export const undoSchemeEdit = (state: SchemeEditorState): SchemeEditorState => {
  const previous = state.history[state.history.length - 1];
  if (!previous) return state;
  return {
    ...state,
    points: clonePoints(previous.points),
    statuses: { ...previous.statuses },
    history: state.history.slice(0, -1),
    future: [...state.future, snapshot(state)],
  };
};

export const redoSchemeEdit = (state: SchemeEditorState): SchemeEditorState => {
  const next = state.future[state.future.length - 1];
  if (!next) return state;
  return {
    ...state,
    points: clonePoints(next.points),
    statuses: { ...next.statuses },
    history: [...state.history, snapshot(state)],
    future: state.future.slice(0, -1),
  };
};

export const resetSchemeEdit = (state: SchemeEditorState): SchemeEditorState => ({
  ...state,
  points: clonePoints(state.baseline),
  statuses: currentStatuses(state.baseline),
  history: [],
  future: [],
});

export const isSchemeDirty = (state: SchemeEditorState) => {
  const baselineIds = state.baseline.map((point) => point.node_id);
  const currentIds = state.points.map((point) => point.node_id);
  return (
    baselineIds.length !== currentIds.length ||
    baselineIds.some((nodeId, index) => nodeId !== currentIds[index])
  );
};

export const toSensorPointRows = (
  state: SchemeEditorState,
): SensorPointRow[] =>
  state.points.map((point, index) => ({
    ...point,
    sequence: index + 1,
    adjustment_status: state.statuses[point.node_id] ?? "current",
  }));

export const summarizeChanges = (state: SchemeEditorState) => {
  const baselineIds = new Set(state.baseline.map((point) => point.node_id));
  const currentIds = new Set(state.points.map((point) => point.node_id));
  const added = [...currentIds].filter((nodeId) => !baselineIds.has(nodeId)).length;
  const removed = [...baselineIds].filter(
    (nodeId) => !currentIds.has(nodeId),
  ).length;
  const replaced = Math.min(added, removed);
  return {
    added: Math.max(0, added - replaced),
    removed: Math.max(0, removed - replaced),
    replaced,
  };
};
