<commit_before>// @flow

import {
  assign,
} from 'power-assign/jsnext'

import {
  createLocalError,
} from 'phenyl-utils/jsnext'

import { removeOne } from './utils'

import type {
  ActionTag,
  Id,
  IdUpdateCommand,
  Entity,
  LocalState,
  PhenylError,
  PushCommand,
  Session,
  UpdateOperation,
  VersionDiff,
} from 'phenyl-interfaces'

import { LocalStateFinder } from './local-state-finder.js'

/**
 *
 */
export class LocalStateUpdater {

  /**
   * Commit the operation of entity to LocalState.
   * Error is thrown when no entity is registered.
   */
  static commit(state: LocalState, command: IdUpdateCommand): UpdateOperation {
    const { entityName, id, operation } = command

    if (!LocalStateFinder.hasEntity(state, { entityName, id })) {
      throw new Error(`LocalStateUpdater.commit(). No entity found. entityName: "${entityName}", id: "${id}".`)
    }
    const entity = LocalStateFinder.getHeadEntity(state, { id, entityName })
    const newEntity = assign(entity, operation)
    return {
      $push: {
        [`entities.${entityName}.${id}.commits`]: operation
      },
      $set: {
        [`entities.${entityName}.${id}.head`]: newEntity,
      }
    }
  }

  /**
   * Revert the already applied commit.
   * Error is thrown when no entity is registered.
   */
  static revert(state: LocalState, command: IdUpdateCommand): UpdateOperation {
    const { entityName, id, operation } = command

    if (!LocalStateFinder.hasEntity(state, { entityName, id })) {
      throw new Error(`LocalStateUpdater.revert(). No entity found. entityName: "${entityName}", id: "${id}".`)
    }
    const entityInfo = LocalStateFinder.getEntityInfo(state, { id, entityName })
    const commits = removeOne(entityInfo.commits, operation)
    const restoredHead = assign(entityInfo.origin, ...commits)
    return {
      $set: {
        [`entities.${entityName}.${id}.commits`]: commits,
        [`entities.${entityName}.${id}.head`]: restoredHead,
      }
    }
  }

  /**
   * Register the entity info into LocalState.
   * Overwrite if already exists.
   */
  static follow(state: LocalState, entityName: string, entity: Entity, versionId: Id): UpdateOperation {
    return {
      $set: {
        [`entities.${entityName}.${entity.id}`]: {
          origin: entity,
          versionId,
          commits: [],
          head: null,
        }
      },
    }
  }

  /**
   * Remove the entity info from LocalState.
   */
  static unfollow(state: LocalState, entityName: string, id: Id): UpdateOperation {
    return {
      $unset: {
        [`entities.${entityName}.${id}`]: ''
      }
    }
  }

  /**
   * Push network request promise.
   */
  static networkRequest(state: LocalState, tag: ActionTag): UpdateOperation {
    return {
      $push: { 'network.requests': tag }
    }
  }

  /**
   * Remove network request promise from the request queue.
   */
  static removeNetworkRequest(state: LocalState, tag: ActionTag): UpdateOperation {
    return {
      $set: { 'network.requests': removeOne(state.network.requests, tag) }
    }
  }

  /**
   * Apply the given VersionDiff as a patch.
   * If the diff's prevVersionId isn't equal to registered versionId, no operation is returned.
   * If it equals, applied to origin.
   */
  static patch(state: LocalState, versionDiff: VersionDiff): UpdateOperation {
    const { entityName, id, versionId, prevVersionId, operation } = versionDiff
    const entityInfo = LocalStateFinder.getEntityInfo(state, { id, entityName })

    // Not applicable diff.
    if (entityInfo.versionId !== prevVersionId) {
      return {}
    }

    const newOrigin = assign(entityInfo.origin, operation)
    const newHead = assign(newOrigin, ...entityInfo.commits)
    return {
      $set: {
        [`entities.${entityName}.${id}.origin`]: newOrigin,
        [`entities.${entityName}.${id}.versionId`]: versionId,
        [`entities.${entityName}.${id}.head`]: newHead,
      }
    }
  }

  /**
   * Apply the master commits.
   * If local commits exist, apply them after master commits.
   */
  static rebase(state: LocalState, pushCommand: PushCommand): UpdateOperation {
    const { entityName, id, versionId, operations } = pushCommand
    const entityInfo = LocalStateFinder.getEntityInfo(state, { id, entityName })

    const newOrigin = assign(entityInfo.origin, ...operations)
    const newHead = assign(newOrigin, ...entityInfo.commits)

    return {
      $set: {
        [`entities.${entityName}.${id}.origin`]: newOrigin,
        [`entities.${entityName}.${id}.versionId`]: versionId,
        [`entities.${entityName}.${id}.head`]: newHead,
      }
    }
  }

  /**
   * Apply the master commits, then apply the given local commits.
   */
  static synchronize(state: LocalState, pushCommand: PushCommand, localCommits: Array<UpdateOperation>): UpdateOperation {
    const { entityName, id, operations, versionId } = pushCommand<commit_msg>[phenyl-redux] Set head to null if commit is empty<commit_after>// @flow

import {
  assign,
} from 'power-assign/jsnext'

import {
  createLocalError,
} from 'phenyl-utils/jsnext'

import { removeOne } from './utils'

import type {
  ActionTag,
  Id,
  IdUpdateCommand,
  Entity,
  LocalState,
  PhenylError,
  PushCommand,
  Session,
  UpdateOperation,
  VersionDiff,
} from 'phenyl-interfaces'

import { LocalStateFinder } from './local-state-finder.js'

/**
 *
 */
export class LocalStateUpdater {

  /**
   * Commit the operation of entity to LocalState.
   * Error is thrown when no entity is registered.
   */
  static commit(state: LocalState, command: IdUpdateCommand): UpdateOperation {
    const { entityName, id, operation } = command

    if (!LocalStateFinder.hasEntity(state, { entityName, id })) {
      throw new Error(`LocalStateUpdater.commit(). No entity found. entityName: "${entityName}", id: "${id}".`)
    }
    const entity = LocalStateFinder.getHeadEntity(state, { id, entityName })
    const newEntity = assign(entity, operation)
    return {
      $push: {
        [`entities.${entityName}.${id}.commits`]: operation
      },
      $set: {
        [`entities.${entityName}.${id}.head`]: newEntity,
      }
    }
  }

  /**
   * Revert the already applied commit.
   * Error is thrown when no entity is registered.
   */
  static revert(state: LocalState, command: IdUpdateCommand): UpdateOperation {
    const { entityName, id, operation } = command

    if (!LocalStateFinder.hasEntity(state, { entityName, id })) {
      throw new Error(`LocalStateUpdater.revert(). No entity found. entityName: "${entityName}", id: "${id}".`)
    }
    const entityInfo = LocalStateFinder.getEntityInfo(state, { id, entityName })
    const commits = removeOne(entityInfo.commits, operation)
    const restoredHead = assign(entityInfo.origin, ...commits)
    return {
      $set: {
        [`entities.${entityName}.${id}.commits`]: commits,
        [`entities.${entityName}.${id}.head`]: restoredHead,
      }
    }
  }

  /**
   * Register the entity info into LocalState.
   * Overwrite if already exists.
   */
  static follow(state: LocalState, entityName: string, entity: Entity, versionId: Id): UpdateOperation {
    return {
      $set: {
        [`entities.${entityName}.${entity.id}`]: {
          origin: entity,
          versionId,
          commits: [],
          head: null,
        }
      },
    }
  }

  /**
   * Remove the entity info from LocalState.
   */
  static unfollow(state: LocalState, entityName: string, id: Id): UpdateOperation {
    return {
      $unset: {
        [`entities.${entityName}.${id}`]: ''
      }
    }
  }

  /**
   * Push network request promise.
   */
  static networkRequest(state: LocalState, tag: ActionTag): UpdateOperation {
    return {
      $push: { 'network.requests': tag }
    }
  }

  /**
   * Remove network request promise from the request queue.
   */
  static removeNetworkRequest(state: LocalState, tag: ActionTag): UpdateOperation {
    return {
      $set: { 'network.requests': removeOne(state.network.requests, tag) }
    }
  }

  /**
   * Apply the given VersionDiff as a patch.
   * If the diff's prevVersionId isn't equal to registered versionId, no operation is returned.
   * If it equals, applied to origin.
   */
  static patch(state: LocalState, versionDiff: VersionDiff): UpdateOperation {
    const { entityName, id, versionId, prevVersionId, operation } = versionDiff
    const entityInfo = LocalStateFinder.getEntityInfo(state, { id, entityName })

    // Not applicable diff.
    if (entityInfo.versionId !== prevVersionId) {
      return {}
    }

    const newOrigin = assign(entityInfo.origin, operation)
    const newHead = assign(newOrigin, ...entityInfo.commits)
    return {
      $set: {
        [`entities.${entityName}.${id}.origin`]: newOrigin,
        [`entities.${entityName}.${id}.versionId`]: versionId,
        [`entities.${entityName}.${id}.head`]: newHead,
      }
    }
  }

  /**
   * Apply the master commits.
   * If local commits exist, apply them after master commits.
   */
  static rebase(state: LocalState, pushCommand: PushCommand): UpdateOperation {
    const { entityName, id, versionId, operations } = pushCommand
    const entityInfo = LocalStateFinder.getEntityInfo(state, { id, entityName })

    const newOrigin = assign(entityInfo.origin, ...operations)
    const newHead = entityInfo.commits.length > 0 ? assign(newOrigin, ...entityInfo.commits) : null

    return {
      $set: {
        [`entities.${entityName}.${id}.origin`]: newOrigin,
        [`entities.${entityName}.${id}.versionId`]: versionId,
        [`entities.${entityName}.${id}.head`]: newHead,
      }
    }
  }

  /**
   * Apply the master commits, then apply the given local commits.
   */
  static synchronize(state: LocalState, pushCommand: PushCommand, localCommits: Array<UpdateOperation>): UpdateOperation {
    const { entityName, id, operations, versionId } = pushCommand
{'fraction_non_alphanumeric': 0.1354251012145749, 'fraction_numerical': 0.00010121457489878542, 'mean_word_length': 3.0166666666666666, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 6, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6880741', 'n_tokens_mistral': 2947, 'n_tokens_neox': 2948, 'n_words': 738}