import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'

import AuditAssetList from '../../components/audit/AuditAssetList.vue'
import AuditJsonRiskRuleDetail from '../../components/audit/AuditJsonRiskRuleDetail.vue'
import AuditRuleDialogs from '../../components/audit/AuditRuleDialogs.vue'
import AuditSpreadsheetChangeDrawer from '../../components/audit/AuditSpreadsheetChangeDrawer.vue'
import AuditSpreadsheetRuleDetail from '../../components/audit/AuditSpreadsheetRuleDetail.vue'
import AuditVersionTimelineDrawer from '../../components/audit/AuditVersionTimelineDrawer.vue'
import TableLoadingState from '../../components/shared/TableLoadingState.vue'
import { useSystemState } from '../../composables/useSystemState.js'
import { useToast } from '../../composables/useToast.js'
import { useAuditAssetData } from './useAuditAssetData.js'
import { buildAuditDetailTopBar } from './auditViewDetailTopBar.js'
import { useAuditListFilters } from './auditViewListFilters.js'
import { useAuditViewPermissions } from './useAuditViewPermissions.js'
import { useAuditRuleReviewFlow } from './useAuditRuleReviewFlow.js'
import { useAuditRuleVersionActions } from './useAuditRuleVersionActions.js'
import { useAuditRiskRuleActions } from './useAuditRiskRuleActions.js'
import { useAuditRiskRuleCreateFlow } from './useAuditRiskRuleCreateFlow.js'
import { useAuditRiskRuleJsonEditor } from './useAuditRiskRuleJsonEditor.js'
import { useAuditReleaseMonitor } from './useAuditReleaseMonitor.js'
import { useAuditSpreadsheetEditor } from './useAuditSpreadsheetEditor.js'
import { useAuditVersionTimeline } from './useAuditVersionTimeline.js'

import {
  TAB_META,
  STATUS_OPTIONS,
  ENABLED_STATE_OPTIONS,
  ONLINE_STATE_OPTIONS,
  RISK_SCENARIO_OPTIONS,
  normalizeText,
  readConfigJson,
  buildPreviewRuleDetail,
} from './auditViewModel.js'
import {
  RISK_RULE_BUSINESS_STAGE_OPTIONS,
  RISK_RULE_EXPENSE_CATEGORY_OPTIONS,
} from './auditViewRiskRuleModel.js'

export default {
  name: 'AuditView',
  components: {
    AuditAssetList,
    AuditJsonRiskRuleDetail,
    AuditRuleDialogs,
    AuditSpreadsheetChangeDrawer,
    AuditSpreadsheetRuleDetail,
    AuditVersionTimelineDrawer,
    TableLoadingState
  },
  emits: ['detail-open-change', 'detail-topbar-change'],
  setup(_, { emit }) {
    const { toast } = useToast()
    const { currentUser } = useSystemState()

    const tabs = Object.entries(TAB_META).map(([id, meta]) => ({
      id,
      label: meta.label
    }))

    const activeType = ref('financialRules')
    const selectedSkill = ref(null)
    const keyword = ref('')
    const activeFilterPopover = ref('')
    const selectedDomain = ref('')
    const selectedOwner = ref('')
    const selectedRiskLevel = ref('')
    const selectedStatus = ref('')
    const selectedRiskScenario = ref('')
    const selectedOnlineState = ref('')
    const selectedEnabledState = ref('')
    const actionState = ref('')
    const riskRuleAttachmentOptions = [
      { label: '是', value: true },
      { label: '否', value: false }
    ]

    const activeMeta = computed(() => TAB_META[activeType.value])
    const activeTabLabel = computed(() => activeMeta.value.label)
    const searchPlaceholder = computed(() => activeMeta.value.searchPlaceholder)
    const createButtonLabel = computed(() => activeMeta.value.createButtonLabel)
    const hintText = computed(() => activeMeta.value.hintText)
    const tableColumns = computed(() => activeMeta.value.tableColumns)
    const showRuntimeColumn = computed(() => activeMeta.value.showRuntimeColumn !== false)
    const showMetricColumn = computed(() => activeMeta.value.showMetricColumn !== false)
    const showVersionColumn = computed(() => activeMeta.value.showVersionColumn !== false)
    const showStatusColumn = computed(() => activeMeta.value.showStatusColumn !== false)
    const showOnlineColumn = computed(() => false)
    const showEnabledColumn = computed(() => activeMeta.value.showEnabledColumn === true)
    const {
      detailBusy,
      isAdmin,
      selectedSkillIsRule,
      selectedSkillUsesSpreadsheet,
      selectedSkillUsesJsonRisk,
      canManageSelected,
      canAdminOperateSelected,
      canEditSelected,
      latestRiskRuleTestSummary,
      riskRuleTestPassed,
      riskRuleInReview,
      riskRuleGenerationBusy,
      riskRuleGenerationFailed,
      canOpenRiskRuleTest,
      canDeleteRiskRule,
      canOpenRiskRuleReviewSubmit,
      canSubmitRiskRuleReview,
      canReturnRiskRule,
      riskRuleHasPublishableRevision,
      canPublishRiskRule,
      canToggleRiskRuleEnabled,
      canEditRiskRuleDraft,
      canCreateRiskRuleRevision,
      canEditMarkdown,
      isDisplayingWorkingVersion,
      canUploadSpreadsheet,
      canDownloadSpreadsheet,
      canEditSpreadsheetInline,
      selectedSpreadsheetFileName
    } = useAuditViewPermissions({
      currentUser,
      selectedSkill,
      actionState
    })
    const {
      versionSwitchTarget,
      versionTimelineOpen,
      versionTimelineLoading,
      versionTimelineError,
      selectedVersionTimelineItems,
      loadVersionTimeline: loadVersionTimelineInState,
      openVersionTimeline: openVersionTimelineInState,
      closeVersionTimeline: closeVersionTimelineInState,
      clearVersionTimelineState,
      openVersionSwitch: openVersionSwitchInState,
      cancelVersionSwitch: cancelVersionSwitchInState,
      confirmVersionSwitch: confirmVersionSwitchInState
    } = useAuditVersionTimeline({
      selectedSkill,
      toast
    })
    const {
      loadRiskRuleJson,
      saveRiskRuleJson,
      formatRiskRuleJson,
      downloadRiskRuleJson
    } = useAuditRiskRuleJsonEditor({
      selectedSkill,
      canEditMarkdown,
      actionState,
      toast
    })
    const {
      loading,
      errorMessage,
      detailLoading,
      detailError,
      assetBuckets,
      currentAssets,
      loadRuns,
      loadAssets,
      refreshCurrentAssets,
      loadSelectedAssetDetail,
      mergeSelectedRuleLifecycle
    } = useAuditAssetData({
      activeType,
      activeMeta,
      selectedSkill,
      loadVersionTimeline: loadVersionTimelineInState,
      loadSpreadsheetChangeRecords: (...args) => loadSpreadsheetChangeRecords(...args),
      loadRiskRuleJson,
      toast
    })
    const {
      spreadsheetUploadInput,
      spreadsheetOnlyOfficeLoading,
      spreadsheetOnlyOfficeError,
      spreadsheetOnlyOfficeReady,
      spreadsheetOnlyOfficeHostId,
      spreadsheetChangeDetailOpen,
      selectedSpreadsheetChangeRecord,
      selectedSpreadsheetChangeRecords,
      selectedSpreadsheetChangeSheetRows,
      selectedSpreadsheetChangeCellRows,
      destroySpreadsheetOnlyOfficeEditor,
      mountSpreadsheetOnlyOfficeEditor,
      triggerSpreadsheetUpload,
      downloadSpreadsheetFile,
      handleSpreadsheetFileInput,
      loadSpreadsheetChangeRecords,
      openSpreadsheetChangeDetail,
      closeSpreadsheetChangeDetail
    } = useAuditSpreadsheetEditor({
      selectedSkill,
      selectedSkillUsesSpreadsheet,
      canEditSpreadsheetInline,
      canUploadSpreadsheet,
      canDownloadSpreadsheet,
      selectedSpreadsheetFileName,
      actionState,
      refreshCurrentAssets,
      loadSelectedAssetDetail,
      resolveActor,
      toast
    })
    const {
      canCreateRiskRule,
      riskRuleCreateOpen,
      riskRuleCreateForm,
      riskRuleCreateBusy,
      openRiskRuleCreateDialog,
      closeRiskRuleCreateDialog,
      submitRiskRuleCreate,
      stopAllRiskRuleGenerationPolls
    } = useAuditRiskRuleCreateFlow({
      activeType,
      isRuleManager,
      detailBusy,
      actionState,
      assetBuckets,
      refreshCurrentAssets,
      resolveActor,
      toast
    })
    const {
      saveRuleMarkdown,
      saveRuleRuntimeJson,
      activateSelectedRule,
      restoreSelectedVersion
    } = useAuditRuleVersionActions({
      selectedSkill,
      selectedSkillIsRule,
      canEditMarkdown,
      canManageSelected,
      actionState,
      detailBusy,
      refreshCurrentAssets,
      loadSelectedAssetDetail,
      resolveActor,
      toast
    })
    const {
      reviewSubmitOpen,
      reviewSubmitVersion,
      reviewSubmitReviewer,
      reviewSubmitReviewerLoading,
      reviewSubmitReviewerOptions,
      canSubmitReview,
      hasReviewSubmitReviewers,
      canReviewSelected,
      reviewSelectedRule,
      openSubmitReviewDialog,
      closeSubmitReviewDialog,
      submitSelectedRuleForReview
    } = useAuditRuleReviewFlow({
      selectedSkill,
      selectedSkillIsRule,
      selectedSkillUsesJsonRisk,
      canEditSelected,
      canManageSelected,
      isDisplayingWorkingVersion,
      canOpenRiskRuleReviewSubmit,
      riskRuleTestPassed,
      detailBusy,
      actionState,
      refreshCurrentAssets,
      loadSelectedAssetDetail,
      resolveActor,
      toast
    })
    const {
      riskRuleTestOpen,
      riskRuleDeleteOpen,
      riskRuleReturnOpen,
      riskRulePublishOpen,
      riskRuleReturnNote,
      riskRuleEditOpen,
      riskRuleEditMode,
      riskRuleEditForm,
      resetRiskRuleActionDialogs,
      openRiskRuleTestDialog,
      closeRiskRuleTestDialog,
      handleRiskRuleReportSaved,
      openRiskRuleEditDialog,
      closeRiskRuleEditDialog,
      submitRiskRuleEdit,
      openDeleteRiskRuleDialog,
      closeDeleteRiskRuleDialog,
      deleteSelectedRiskRule,
      openReturnRiskRuleDialog,
      closeReturnRiskRuleDialog,
      returnSelectedRiskRule,
      openPublishRiskRuleDialog,
      closePublishRiskRuleDialog,
      publishSelectedRiskRule,
      toggleSelectedRiskRuleEnabled
    } = useAuditRiskRuleActions({
      selectedSkill,
      detailBusy,
      actionState,
      canOpenRiskRuleTest,
      canDeleteRiskRule,
      canReturnRiskRule,
      canPublishRiskRule,
      riskRuleHasPublishableRevision,
      canToggleRiskRuleEnabled,
      canEditRiskRuleDraft,
      canCreateRiskRuleRevision,
      riskRuleTestPassed,
      refreshCurrentAssets,
      loadSelectedAssetDetail,
      mergeSelectedRuleLifecycle,
      closeDetail,
      resolveActor,
      toast
    })
    const {
      releaseState,
      releaseQueue,
      releaseLoading,
      releaseError,
      releaseAction,
      loadReleaseMonitor,
      submitReleaseLabel
    } = useAuditReleaseMonitor({
      selectedSkill,
      canManageSelected,
      toast
    })
    const showReviewNote = computed(
      () => selectedSkillIsRule.value && (selectedSkill.value?.reviewNote || selectedSkill.value?.reviewTimeLabel)
    )
    const {
      activeFilterTokens,
      auditEmptyState,
      domainOptions,
      ownerOptions,
      riskLevelOptions,
      selectedDomainLabel,
      selectedEnabledStateLabel,
      selectedOnlineStateLabel,
      selectedOwnerLabel,
      selectedRiskLevelLabel,
      selectedRiskScenarioLabel,
      selectedStatusLabel,
      showEnabledFilter,
      showOnlineFilter,
      showOwnerFilter,
      showRiskLevelFilter,
      showRiskScenarioFilter,
      showStatusFilter,
      visibleSkills
    } = useAuditListFilters({
      activeType,
      activeTabLabel,
      currentAssets,
      keyword,
      selectedDomain,
      selectedEnabledState,
      selectedOnlineState,
      selectedOwner,
      selectedRiskLevel,
      selectedRiskScenario,
      selectedStatus
    })
    const canActivateSelected = computed(() => {
      if (!selectedSkillIsRule.value || !canManageSelected.value || detailBusy.value) {
        return false
      }

      return (
        isDisplayingWorkingVersion.value &&
        selectedSkill.value?.reviewStatusValue === 'approved' &&
        selectedSkill.value?.workingVersion !== selectedSkill.value?.publishedVersion
      )
    })
    const activateBlockedReason = computed(() => {
      if (!selectedSkillIsRule.value) {
        return ''
      }
      if (selectedSkill.value?.isPreviewMock) {
        return '当前为页面预览态，暂不执行真实审核和上线。'
      }
      if (!canManageSelected.value) {
        return '仅高级财务人员可执行审核和上线。'
      }
      if (!isDisplayingWorkingVersion.value) {
        return '请先切回当前工作版本，再执行审核或上线。'
      }
      if (selectedSkill.value?.workingVersion === selectedSkill.value?.publishedVersion) {
        return '当前工作版本已经是线上版本。'
      }
      if (selectedSkill.value?.reviewStatusValue !== 'approved') {
        return '当前规则版本未审核通过，不能上线。'
      }
      return ''
    })
    const auditDetailTopBar = computed(() =>
      buildAuditDetailTopBar({
        skill: selectedSkill.value,
        usesJsonRiskRule: selectedSkillUsesJsonRisk.value
      })
    )

    watch(
      selectedSkill,
      (value) => {
        emit('detail-open-change', Boolean(value))
      },
      { immediate: true }
    )

    watch(
      auditDetailTopBar,
      (value) => {
        emit('detail-topbar-change', value)
      },
      { immediate: true, deep: true }
    )

    watch(
      () => [
        selectedSkill.value?.id || '',
        selectedSkill.value?.loading ? '1' : '0',
        selectedSkill.value?.usesSpreadsheetRule ? '1' : '0'
      ],
      async () => {
        if (!selectedSkillUsesSpreadsheet.value || selectedSkill.value?.loading) {
          destroySpreadsheetOnlyOfficeEditor()
          spreadsheetOnlyOfficeError.value = ''
          spreadsheetOnlyOfficeLoading.value = false
          return
        }
        await mountSpreadsheetOnlyOfficeEditor()
      }
    )

    watch(activeType, () => {
      destroySpreadsheetOnlyOfficeEditor()
      selectedSkill.value = null
      clearVersionTimelineState()
      resetFilters()
      loadAssets({ force: true }).catch((error) => {
        errorMessage.value = error?.message || '资产数据加载失败，请稍后重试。'
      })
    })

    function resetFilters() {
      keyword.value = ''
      selectedDomain.value = ''
      selectedOwner.value = ''
      selectedRiskLevel.value = ''
      selectedStatus.value = ''
      selectedRiskScenario.value = ''
      selectedOnlineState.value = ''
      selectedEnabledState.value = ''
      activeFilterPopover.value = ''
    }

    function handleAuditEmptyAction() {
      if (!currentAssets.value.length || !activeFilterTokens.value.length) {
        loadAssets({ force: true }).catch(() => {})
        return
      }

      resetFilters()
    }

    function toggleFilterPopover(name) {
      activeFilterPopover.value = activeFilterPopover.value === name ? '' : name
    }

    function closeFilterPopover() {
      activeFilterPopover.value = ''
    }

    function selectFilter(name, value) {
      if (name === 'domain') {
        selectedDomain.value = value
      }
      if (name === 'owner') {
        selectedOwner.value = value
      }
      if (name === 'riskLevel') {
        selectedRiskLevel.value = value
      }
      if (name === 'status') {
        selectedStatus.value = value
      }
      if (name === 'riskScenario') {
        selectedRiskScenario.value = value
      }
      if (name === 'online') {
        selectedOnlineState.value = value
      }
      if (name === 'enabled') {
        selectedEnabledState.value = value
      }
      closeFilterPopover()
    }

    function handleDocumentClick(event) {
      const target = event.target
      if (!(target instanceof Element)) {
        closeFilterPopover()
        return
      }
      if (!target.closest('.picker-filter')) {
        closeFilterPopover()
      }
    }

    function resolveActor() {
      return currentUser.value?.name || currentUser.value?.username || 'system'
    }

    function openAssetDetail(asset) {
      if (asset?.usesJsonRiskRule && asset.statusValue === 'generating') {
        toast('规则仍在后台生成中，生成完成后才能进入详情。')
        return
      }
      destroySpreadsheetOnlyOfficeEditor()
      spreadsheetOnlyOfficeError.value = ''
      spreadsheetOnlyOfficeLoading.value = false
      if (asset?.isPreviewMock) {
        selectedSkill.value = buildPreviewRuleDetail()
        detailError.value = ''
        detailLoading.value = false
        clearVersionTimelineState()
        return
      }
      const opensSpreadsheetRule = Boolean(asset?.usesSpreadsheetRule)
      selectedSkill.value = {
        ...asset,
        configJson: {},
        isPreviewMock: false,
        usesSpreadsheetRule: opensSpreadsheetRule,
        usesJsonRiskRule: Boolean(asset?.usesJsonRiskRule),
        riskRuleJsonText: '{}',
        riskRuleSummary: null,
        riskRuleDescription: '',
        riskRuleSourceRef: '',
        ruleDocument: asset?.ruleDocument || null,
        scenarioList: [],
        fields: [],
        promptSections: [],
        outputRules: [],
        tests: [],
        triggers: [],
        tools: [],
        history: [],
        markdownContent: '',
        runtimeRuleText: '',
        ruleTemplateKey: '',
        ruleTemplateLabel: '',
        runtimeKind: 'policy_rule_draft',
        displayVersion: asset.version,
        displayVersionChangeNote: '无版本说明',
        loading: !opensSpreadsheetRule,
        reviewStatusLabel: opensSpreadsheetRule ? '' : '加载中',
        reviewStatusTone: 'draft'
      }
      clearVersionTimelineState()
      if (opensSpreadsheetRule) {
        loadSpreadsheetChangeRecords(asset.id).catch(() => {})
      }
      loadSelectedAssetDetail(asset.id).catch(() => {})
    }

    function closeDetail() {
      destroySpreadsheetOnlyOfficeEditor()
      spreadsheetOnlyOfficeError.value = ''
      spreadsheetOnlyOfficeLoading.value = false
      selectedSkill.value = null
      detailError.value = ''
      detailLoading.value = false
      clearVersionTimelineState()
      resetRiskRuleActionDialogs()
    }

    onMounted(() => {
      document.addEventListener('click', handleDocumentClick)
      loadAssets({ force: true }).catch(() => {})
      loadRuns().catch(() => {})
    })

    onBeforeUnmount(() => {
      destroySpreadsheetOnlyOfficeEditor()
      stopAllRiskRuleGenerationPolls()
      document.removeEventListener('click', handleDocumentClick)
    })

    return {
      tabs,
      activeType,
      activeTabLabel,
      selectedSkill,
      versionSwitchTarget,
      keyword,
      createButtonLabel,
      hintText,
      searchPlaceholder,
      tableColumns,
      showRuntimeColumn,
      showVersionColumn,
      showMetricColumn,
      showStatusColumn,
      showOnlineColumn,
      showEnabledColumn,
      visibleSkills,
      auditEmptyState,
      loading,
      errorMessage,
      detailLoading,
      detailError,
      selectedDomain,
      selectedOwner,
      selectedRiskLevel,
      selectedStatus,
      selectedRiskScenario,
      selectedOnlineState,
      selectedEnabledState,
      selectedDomainLabel,
      selectedOwnerLabel,
      selectedRiskLevelLabel,
      selectedStatusLabel,
      selectedRiskScenarioLabel,
      selectedOnlineStateLabel,
      selectedEnabledStateLabel,
      showRiskScenarioFilter,
      showOwnerFilter,
      showRiskLevelFilter,
      showStatusFilter,
      showOnlineFilter,
      showEnabledFilter,
      domainOptions,
      ownerOptions,
      riskLevelOptions,
      statusOptions: STATUS_OPTIONS,
      riskScenarioOptions: RISK_SCENARIO_OPTIONS,
      onlineStateOptions: ONLINE_STATE_OPTIONS,
      enabledStateOptions: ENABLED_STATE_OPTIONS,
      activeFilterPopover,
      activeFilterTokens,
      canManageSelected,
      canEditSelected,
      canCreateRiskRule,
      canOpenRiskRuleTest,
      canDeleteRiskRule,
      canOpenRiskRuleReviewSubmit,
      canSubmitRiskRuleReview,
      canReturnRiskRule,
      canPublishRiskRule,
      canToggleRiskRuleEnabled,
      riskRuleTestPassed,
      riskRuleInReview,
      canSubmitReview,
      hasReviewSubmitReviewers,
      canReviewSelected,
      canEditMarkdown,
      canUploadSpreadsheet,
      canDownloadSpreadsheet,
      canEditSpreadsheetInline,
      canActivateSelected,
      activateBlockedReason,
      selectedSkillIsRule,
      selectedSkillUsesSpreadsheet,
      selectedSkillUsesJsonRisk,
      selectedSpreadsheetFileName,
      selectedVersionTimelineItems,
      selectedSpreadsheetChangeRecords,
      detailBusy,
      actionState,
      reviewSubmitOpen,
      reviewSubmitVersion,
      reviewSubmitReviewer,
      reviewSubmitReviewerLoading,
      reviewSubmitReviewerOptions,
      riskRuleAttachmentOptions,
      riskRuleCreateOpen,
      riskRuleCreateForm,
      riskRuleCreateBusy,
      riskRuleTestOpen,
      riskRuleDeleteOpen,
      riskRuleReturnOpen,
      riskRulePublishOpen,
      riskRuleReturnNote,
      riskRuleEditOpen,
      riskRuleEditMode,
      riskRuleEditForm,
      releaseState,
      releaseQueue,
      releaseLoading,
      releaseError,
      releaseAction,
      riskRuleBusinessStageOptions: RISK_RULE_BUSINESS_STAGE_OPTIONS,
      riskRuleExpenseCategoryOptions: RISK_RULE_EXPENSE_CATEGORY_OPTIONS,
      showReviewNote,
      spreadsheetUploadInput,
      spreadsheetOnlyOfficeLoading,
      spreadsheetOnlyOfficeError,
      spreadsheetOnlyOfficeReady,
      spreadsheetOnlyOfficeHostId,
      versionTimelineOpen,
      versionTimelineLoading,
      versionTimelineError,
      spreadsheetChangeDetailOpen,
      selectedSpreadsheetChangeRecord,
      selectedSpreadsheetChangeSheetRows,
      selectedSpreadsheetChangeCellRows,
      openAssetDetail,
      closeDetail,
      resetFilters,
      handleAuditEmptyAction,
      toggleFilterPopover,
      selectFilter,
      closeFilterPopover,
      openRiskRuleCreateDialog,
      closeRiskRuleCreateDialog,
      submitRiskRuleCreate,
      openVersionSwitch: openVersionSwitchInState,
      cancelVersionSwitch: cancelVersionSwitchInState,
      confirmVersionSwitch: confirmVersionSwitchInState,
      saveRuleMarkdown,
      saveRuleRuntimeJson,
      saveRiskRuleJson,
      formatRiskRuleJson,
      downloadRiskRuleJson,
      triggerSpreadsheetUpload,
      downloadSpreadsheetFile,
      handleSpreadsheetFileInput,
      reviewSelectedRule,
      openSubmitReviewDialog,
      closeSubmitReviewDialog,
      submitSelectedRuleForReview,
      openRiskRuleTestDialog,
      closeRiskRuleTestDialog,
      handleRiskRuleReportSaved,
      openRiskRuleEditDialog,
      closeRiskRuleEditDialog,
      submitRiskRuleEdit,
      openDeleteRiskRuleDialog,
      closeDeleteRiskRuleDialog,
      deleteSelectedRiskRule,
      openReturnRiskRuleDialog,
      closeReturnRiskRuleDialog,
      returnSelectedRiskRule,
      openPublishRiskRuleDialog,
      closePublishRiskRuleDialog,
      publishSelectedRiskRule,
      toggleSelectedRiskRuleEnabled,
      loadReleaseMonitor,
      submitReleaseLabel,
      activateSelectedRule,
      restoreSelectedVersion,
      openVersionTimeline: openVersionTimelineInState,
      closeVersionTimeline: closeVersionTimelineInState,
      openSpreadsheetChangeDetail,
      closeSpreadsheetChangeDetail,
      loadAssets
    }
  }
}
