// @ts-nocheck
import { isEqual } from 'lodash'

import { isPendingAddRow } from '../types'
import { generateTableChangeKey, rowMatchesIdentifiers } from './queueOperationUtils'
import { tryParseJson } from '@/lib/helpers'
import {
  isDeleteRowOperation,
  isEditCellContentOperation,
  NewQueuedOperation,
  QueuedOperation,
  QueuedOperationType,
  type NewDeleteRowOperation,
  type NewEditCellContentOperation,
} from '@/state/table-editor-operation-queue.types'

export type DeleteConflictResult =
  | { action: 'skip'; filteredOperations: QueuedOperation[] }
  | { action: 'add'; filteredOperations: QueuedOperation[] }

export type EditConflictResult =
  | { action: 'reject'; reason: string }
  | { action: 'merge'; updatedOperations: QueuedOperation[] }
  | { action: 'add' }

export type UpsertResult = {
  operations: QueuedOperation[]
}

function editOperationMatchesTempId(operation: QueuedOperation, tempId: string): boolean {
  if (!isEditCellContentOperation(operation)) return false
  return operation.payload.rowIdentifiers.__tempId === tempId
}

export function operationMatchesRow(
  operation: QueuedOperation,
  tableId: number,
  rowIdentifiers: Record<string, unknown>
): boolean {
  if (operation.tableId !== tableId) return false

  if (
    operation.type === QueuedOperationType.EDIT_CELL_CONTENT ||
    operation.type === QueuedOperationType.DELETE_ROW
  ) {
    return rowMatchesIdentifiers(operation.payload.rowIdentifiers, rowIdentifiers)
  }

  return false
}

export function resolveDeleteRowConflicts(
  operations: readonly QueuedOperation[],
  deleteOperation: NewDeleteRowOperation
): DeleteConflictResult {
  const rowIdentifiers = deleteOperation.payload.rowIdentifiers

  // Check if this row was newly added (by tempId)
  // If deleting a newly added row, filter out the ADD_ROW operation
  const originalRow = deleteOperation.payload.originalRow
  if (isPendingAddRow(originalRow)) {
    const tempId = originalRow.__tempId
    const addRowKey = generateTableChangeKey({
      type: QueuedOperationType.ADD_ROW,
      tableId: deleteOperation.tableId,
      payload: {
        tempId,
        rowData: originalRow,
        table: deleteOperation.payload.table,
      },
    })

    let filteredOperations = operations
      .filter((op) => op.id !== addRowKey)
      .filter((op) => !editOperationMatchesTempId(op, tempId))

    return { action: 'skip', filteredOperations }
  }

  // For existing rows, remove any pending EDIT_CELL operations for the row being deleted
  const filteredOperations = operations.filter(
    (op) => !operationMatchesRow(op, deleteOperation.tableId, rowIdentifiers)
  )

  return { action: 'add', filteredOperations }
}

export function resolveEditCellConflicts(
  operations: readonly QueuedOperation[],
  editOperation: NewEditCellContentOperation
): EditConflictResult {
  const rowIdentifiers = editOperation.payload.rowIdentifiers

  // Check if this row is pending deletion
  const isPendingDeletion = operations.filter(isDeleteRowOperation).some((op) => {
    if (op.tableId === editOperation.tableId) {
      return Object.entries(op.payload.rowIdentifiers).every(
        ([key, value]) => rowIdentifiers[key] === value
      )
    }
    return false
  })

  if (isPendingDeletion) {
    return {
      action: 'reject',
      reason:
        'Cannot edit a cell on a row that is pending deletion. Remove the delete operation first.',
    }
  }

  // Check if this edit is on a newly added row (by tempId)
  const tempId = rowIdentifiers.__tempId
  if (tempId) {
    const addRowIndex = operations.findIndex((op) => {
      if (op.type === QueuedOperationType.ADD_ROW && op.tableId === editOperation.tableId) {
        return op.payload.tempId === tempId
      }
      return false
    })

    if (addRowIndex >= 0) {
      // Merge the edit into the ADD_ROW's rowData
      const updatedOperations = [...operations]
      const addOp = updatedOperations[addRowIndex]
      if (addOp.type === QueuedOperationType.ADD_ROW) {
        const addPayload = { ...addOp.payload }
        addPayload.rowData = {
          ...addPayload.rowData,
          [editOperation.payload.columnName]: editOperation.payload.newValue,
        }

        updatedOperations[addRowIndex] = {
          ...addOp,
          payload: addPayload,
          timestamp: Date.now(),
        }
      }

      return { action: 'merge', updatedOperations }
    }
  }

  return { action: 'add' }
}

export function upsertOperation(
  operations: readonly QueuedOperation[],
  newOperation: NewQueuedOperation
): UpsertResult {
  const operationKey = generateTableChangeKey(newOperation)
  const existingOpIndex = operations.findIndex((op) => op.id === operationKey)

  const queuedOperation: QueuedOperation = {
    ...newOperation,
    id: operationKey,
    timestamp: Date.now(),
  }

  if (existingOpIndex >= 0) {
    const updatedOperations = [...operations]

    // Keep the old value of the operation that is being overwritten
    // When a user edits the same cell multiple times before saving,
    // we need to preserve the original "before edit" value
    const existingOp = operations[existingOpIndex]
    if (
      queuedOperation.type === QueuedOperationType.EDIT_CELL_CONTENT &&
      existingOp.type === QueuedOperationType.EDIT_CELL_CONTENT
    ) {
      queuedOperation.payload.oldValue = existingOp.payload.oldValue

      const { oldValue, newValue } = queuedOperation.payload
      // [Joshen] These comparisons by data type are because of how the table editor renders the values
      if (
        (typeof oldValue === 'number' && Number(oldValue) === Number(newValue)) ||
        (typeof newValue === 'object' && isEqual(tryParseJson(oldValue), newValue)) ||
        oldValue === newValue
      ) {
        updatedOperations.splice(existingOpIndex, 1)
        return { operations: updatedOperations }
      }
    }

    updatedOperations[existingOpIndex] = queuedOperation
    return { operations: updatedOperations }
  }

  return { operations: [...operations, queuedOperation] }
}
