import { setupLicenseToast } from '@features/license-toast';

import React from 'react';
import { emit, on } from '@ablogcms/events';
import { createEventBus } from '@ablogcms/events/core';
import {
  createPluginRegistry,
  getBlockDefinitionRegistry,
  getBlockExtensionRegistry,
  getColumnTypeRegistry,
  getDataviewActionRegistry,
  getDataviewBulkActionRegistry,
  getDataviewMenuRegistry,
  getGlobalFillBridge,
  getUnitDefinitionRegistry,
} from '@ablogcms/plugins/core';
import './polyfill';
import dispatchSystemUpdate from './features/system-update';
import dispatchBackup from './features/backup';
import dispatchStaticExport from './features/static-export';
import dispatchCsvImport from './features/csv-import';
import dispatchWxrExport from './features/wxr-export';
import dispatchWebpConvert from './features/webp-convert';
import dispatchWebhookEventSelect from './dispatch/dispatch-webhook-event-select';
import dispatchPreviewMode from './dispatch/dispatch-preview-mode';
import dispatchTimeMachineMode from './dispatch/dispatch-timemachine-mode';
import dispatchInlinePreview from './dispatch/dispatch-inline-preview';
import dispatchAdminMenuEditor from './dispatch/dispatch-admin-menu-editor';
import dispatchBannerEditor from './dispatch/dispatch-banner-editor';
import dispatchThemeColorPicker from './dispatch/dispatch-theme-color-picker';
import dispatchSelect2 from './dispatch/dispatch-select2';
import dispatchCustomFieldMaker from './dispatch/dispatch-custom-field-maker';
import dispatchAuditLogDetailModal from './dispatch/dispatch-audit-log-detail-modal';
import dispatchQuickSearch from './dispatch/dispatch-quick-search';
import dispatchEntryLockModal from './dispatch/dispatch-entry-lock-modal';
import dispatchKeyboardShortcutModal from './dispatch/dispatch-keyboard-shortcut-modal';
import dispatchNavigationEditor from './dispatch/dispatch-navigation-editor';
import dispatchMediaAdmin from './dispatch/media/dispatch-media-admin';
import dispatchMediaField from './dispatch/media/dispatch-media-field';
import { bootstrapMediaRuntime } from './features/media/bootstrap';
import dispatchAtableField from './dispatch/dispatch-a-table-field';
import dispatchRichEditor from './dispatch/dispatch-rich-editor';
import dispatchLiteEditorField from './dispatch/dispatch-lite-editor-field';
import dispatchEntryAdmin from './dispatch/dispatch-entry-admin';
import dispatchEntryBulkChangeSelect from './dispatch/dispatch-entry-bulk-change-select';
import dispatchModuleAdmin from './dispatch/dispatch-module-admin';
import dispatchNotify from './dispatch/dispatch-notify';
import dispatchPending from './dispatch/dispatch-pending';
import dispatchTooltip from './dispatch/dispatch-tooltip';
import dispatchGoogleMapsPicker from './dispatch/dispatch-google-maps-picker';
import dispatchOpenStreetMapPicker from './dispatch/dispatch-open-street-map-picker';
import dispatchModal from './dispatch/dispatch-modal';
import dispatchDrawer from './dispatch/dispatch-drawer';
import dispatchResizeImageCF from './dispatch/dispatch-resize-image-cf';
import dispatchDialog from './dispatch/dispatch-dialog';
import { renderModuleInfo } from './features/module-dialog/render-module-info';
import { registerAcmsElements } from './elements/register';
import { upgradeLegacyBlockEditors } from './elements/upgrade-legacy-marks';

(function (global) {
  global.ACMS.Library = global.ACMS.Library || {};

  global.ACMS.Library.React = React;
  global.ACMS.Library.renderModuleInfo = renderModuleInfo;

  // バス本体は index.js（公開側バンドル、admin.js より前提条件が緩く常に読み込まれる）側で
  // 生成済みのはずだが、稀な読み込み順の乱れに備えて存在しない場合のみここでも生成する
  // （index.js 側のコメント参照。「プラグインの登録 → core の消費」の順序保証はどちらが
  // 生成しても変わらない）。
  global.ACMS.events = global.ACMS.events || createEventBus();

  // ctx.ui.fill / ctx.unitEditor.registerUnitDefinition / ctx.dataview.registerColumnType の書き込み先を、
  // ページ全体で 1 つのシングルトンに固定する（@ablogcms/plugins/core の global-singletons）。
  // 管理画面は dispatcher ごとに独立した React ルートを createRoot するため、後続フェーズで
  // 各 dispatcher が <SlotFillProvider store={getGlobalFillBridge()}> を張るだけで、
  // どのツリーからも同じ登録内容を参照できるようにするための結線。
  global.ACMS.plugins = createPluginRegistry({
    eventsBus: global.ACMS.events,
    fillStore: getGlobalFillBridge(),
    unitDefinitionRegistry: getUnitDefinitionRegistry(),
    columnTypeRegistry: getColumnTypeRegistry(),
    blockDefinitionRegistry: getBlockDefinitionRegistry(),
    blockExtensionRegistry: getBlockExtensionRegistry(),
    dataviewActionRegistry: getDataviewActionRegistry(),
    dataviewBulkActionRegistry: getDataviewBulkActionRegistry(),
    dataviewMenuRegistry: getDataviewMenuRegistry(),
  });
})(window);
ACMS.Ready(() => {
  __webpack_public_path__ = ACMS.Config.root; // eslint-disable-line

  /**
   * @ablogcms/media の実行時依存（データ取得）を注入する。
   * 同パッケージはバンドル内で単一モジュールとして共有されるため、ここで一度呼べば
   * メディアライブラリ本体だけでなく、リッチエディタ／ブロックエディタのメディア挿入・
   * バナーエディタ・ナビゲーションエディタ等、@ablogcms/media を使うすべての箇所に行き渡る。
   * 後続のカスタム要素登録・各 dispatch より前（このコールバックの先頭）で呼ぶこと。
   */
  bootstrapMediaRuntime();

  /**
   * カスタム要素（acms-tag-select / acms-category-select / acms-sub-category-select /
   * acms-related-entry / acms-unit-editor / acms-block-editor）の登録。
   *
   * data-target 属性が指す外部 input を DOM から解決するため、ドキュメント全体の構築完了
   * （acmsReady、実質 DOMContentLoaded 相当）を待ってから customElements.define する必要がある
   * （admin バンドルは <head> に async defer で読み込まれ、パース途中に実行され得るため）。
   * 一度 define されればページ内の残りの要素は customElements.define は冪等に扱われる。
   * 以降に動的挿入される要素（カスタムフィールドの追加やタブ・ダイアログの遅延コンテンツ）が
   * 最初からいずれかのカスタム要素タグである場合は、ブラウザの Custom Elements 標準機能により
   * 自動的に connectedCallback が呼ばれるため dispatch によるマーク走査は不要。ただし
   * `.js-block-editor`（公開仕様の旧マーク、`upgradeLegacyBlockEditors` 参照）は素の `<div>` として
   * 挿入されるため対象外で、別途 acmsAdminDelayedContents を購読して都度アップグレードする
   * （このブロックより後の「BlockEditor, Rich Editor」セクションを参照）。
   */
  registerAcmsElements();

  /**
   * Delayed contents event
   *
   * 発火元（`ablogcms/js/dispatch/admin.js` / `dialog.js` 等のレガシー jQuery 領域）は
   * 新バス（`tab.select.change` 等）とレガシー DOM イベント（`acmsAdminSelectTab` 等）を
   * 二重発火済みのため、ここでの中継は新バス側を購読する（`ACMS.addListener` に依存しない）。
   * レガシー `acmsAdminDelayedContents` への転送は後方互換のため維持する。
   */
  on('tab.select.change', ({ tab }) => {
    void emit('admin.delayed-contents.change', { target: tab, detail: undefined });
    ACMS.dispatchEvent('acmsAdminDelayedContents', tab, undefined);
  });
  on('tab.show.after', ({ tab, panel }) => {
    void emit('admin.delayed-contents.change', { target: tab, detail: { tab, panel } });
    ACMS.dispatchEvent('acmsAdminDelayedContents', tab, { tab, panel });
  });
  on('dialog.open.after', ({ element }) => {
    void emit('admin.delayed-contents.change', { target: element, detail: { item: element } });
    ACMS.dispatchEvent('acmsAdminDelayedContents', element, { item: element });
  });
  on('field-group.add.after', ({ element }) => {
    void emit('admin.delayed-contents.change', { target: element, detail: { item: element } });
    ACMS.dispatchEvent('acmsAdminDelayedContents', element, { item: element });
  });

  /**
   * モーダルないのいらない情報を削除
   */
  on('dialog.open.after', ({ element }) => {
    $('.js-hide-on-modal', element).remove();
  });

  /**
   * 通知
   */
  dispatchNotify();

  /**
   * ダイアログ
   */
  dispatchDialog();

  /**
   * ツールチップ
   */
  dispatchTooltip();

  /*
   * ペンディング
   */
  dispatchPending();

  /**
   * モーダル
   */
  dispatchModal();

  /**
   * ドロワー
   */
  dispatchDrawer();

  /**
   * a-table field
   */
  dispatchAtableField(document);
  on('admin.delayed-contents.change', () => {
    dispatchAtableField(document);
  });
  on('custom-field-maker.preview.after', ({ element }) => {
    dispatchAtableField(element);
  });

  /**
   * BlockEditor, Rich Editor
   *
   * upgradeLegacyBlockEditors は `.js-block-editor`（公開仕様の旧マーク）を見つけ次第
   * `<acms-block-editor>` へ置き換える。以後の初期化はカスタム要素の connectedCallback が
   * 引き継ぐため、都度 React をマウントし直す処理はここでは行わない。
   */
  upgradeLegacyBlockEditors(document);
  dispatchRichEditor(document);
  on('admin.delayed-contents.change', ({ target, detail }) => {
    const context = detail?.item || target;
    upgradeLegacyBlockEditors(context);
    dispatchRichEditor(context);
  });
  on('custom-field-maker.preview.after', ({ element }) => {
    upgradeLegacyBlockEditors(element);
    dispatchRichEditor(element);
  });

  /**
   * Lite Editor Field
   */
  dispatchLiteEditorField(document);
  on('admin.delayed-contents.change', ({ target }) => {
    const ctx = target || document;
    dispatchLiteEditorField(ctx);
  });
  on('field-group.add.after', ({ element }) => {
    dispatchLiteEditorField(element);
  });
  on('custom-field-maker.preview.after', ({ element }) => {
    dispatchLiteEditorField(element);
  });

  /**
   * カスタムフィールドメーカー
   */
  dispatchCustomFieldMaker(document);

  /**
   * Google Map Picker
   */
  dispatchGoogleMapsPicker(document);
  on('field-group.add.after', ({ element }) => {
    dispatchGoogleMapsPicker(element);
  });

  on('dialog.open.after', ({ element }) => {
    dispatchGoogleMapsPicker(element);
  });

  dispatchOpenStreetMapPicker(document);
  on('field-group.add.after', ({ element }) => {
    dispatchOpenStreetMapPicker(element);
  });

  on('dialog.open.after', ({ element }) => {
    dispatchOpenStreetMapPicker(element);
  });

  /**
   * メニューのスクロールバー（IE以外）
   */
  (async () => {
    if (!/^ie/.test(ACMS.Dispatch.Utility.getBrowser())) {
      const { default: PerfectScrollbar } = await import(
        /* webpackChunkName: "perfect-scrollbar" */ 'perfect-scrollbar'
      );
      await import(/* webpackChunkName: "perfect-scrollbar-css" */ 'perfect-scrollbar/css/perfect-scrollbar.css');
      const psDom = document.querySelector('.js-scroll-contents');
      if (psDom) {
        const ps = new PerfectScrollbar(psDom, {
          wheelSpeed: 1,
          wheelPropagation: true,
          minScrollbarLength: 20,
        });
        ps.update();
      }
    }
  })();

  /**
   * API管理画面のAPI KEY生成
   */
  (() => {
    const target = document.querySelector('.js-x-api-key');
    const generateUuid = () => {
      // https://github.com/GoogleChrome/chrome-platform-analytics/blob/master/src/internal/identifier.js
      // const FORMAT: string = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
      const chars = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.split('');
      for (let i = 0, len = chars.length; i < len; i++) {
        switch (chars[i]) {
          case 'x':
            chars[i] = Math.floor(Math.random() * 16).toString(16);
            break;
          case 'y':
            chars[i] = (Math.floor(Math.random() * 4) + 8).toString(16);
            break;
        }
      }
      return chars.join('');
    };
    if (target) {
      const input = target.querySelector('.js-key-input');
      const updateButton = target.querySelector('.js-update-key');
      if (input && !input.value) {
        input.value = generateUuid();
      }
      if (updateButton) {
        updateButton.addEventListener('click', (e) => {
          e.preventDefault();
          if (window.confirm('本当にキーを更新してもよろしいですか？保存するまでは変更されません。')) {
            input.value = generateUuid();
          }
        });
      }
    }
  })();

  /**
   * 監査ログページ
   */
  dispatchAuditLogDetailModal(document);

  /**
   * Webhook 管理ページ
   */
  dispatchWebhookEventSelect(document);

  /**
   * Theme color picker
   */
  dispatchThemeColorPicker(document);
  on('admin.delayed-contents.change', ({ target, detail }) => {
    const context = detail?.item || target;
    dispatchThemeColorPicker(context);
  });

  /**
   * クイックサーチ
   */
  dispatchQuickSearch(document);

  /**
   * エントリーロック
   */
  if (ACMS.Config.edition === 'professional' || ACMS.Config.edition === 'enterprise') {
    dispatchEntryLockModal(document);
  }

  /**
   * キーボードショートカットモーダル
   */
  dispatchKeyboardShortcutModal(document);

  /**
   * プレビューモード（インライン）
   */
  dispatchInlinePreview(document);

  /**
   * プレビューモード（モーダル）
   */
  dispatchPreviewMode(document);

  /**
   * タイムマシーンモード
   */
  dispatchTimeMachineMode(document);

  /**
   * System update
   */
  dispatchSystemUpdate(document);

  /**
   * Backup
   */
  dispatchBackup(document);

  /**
   * Static export
   */
  dispatchStaticExport(document);

  /**
   * CSV Import
   */
  dispatchCsvImport(document);

  /**
   * WXR Export
   */
  dispatchWxrExport(document);

  /**
   * WebP Convert
   */
  dispatchWebpConvert(document);

  /**
   * Select2
   */
  dispatchSelect2(document);
  on('admin.delayed-contents.change', ({ target }) => {
    dispatchSelect2(target);
  });
  on('field-group.add.after', ({ element }) => {
    dispatchSelect2(element);
  });

  /**
   * Resize image cf
   */
  dispatchResizeImageCF(document);
  on('field-group.add.after', ({ element }) => {
    dispatchResizeImageCF(element);
  });

  /**
   * Disclose password
   */
  const disclosePassword = document.querySelectorAll('.js-disclose_password');
  [].forEach.call(disclosePassword, (item) => {
    item.addEventListener('change', (event) => {
      const selector = event.target.getAttribute('data-target');
      const target = document.querySelector(selector);
      if (event.target.checked) {
        target.setAttribute('type', 'text');
      } else {
        target.setAttribute('type', 'password');
      }
    });
  });

  /**
   * AdminMenu editor
   */
  dispatchAdminMenuEditor(document);
  on('admin.delayed-contents.change', ({ target, detail }) => {
    const context = detail?.item || target;
    dispatchAdminMenuEditor(context);
  });

  /**
   * Navigation editor
   */
  dispatchNavigationEditor(document);
  on('admin.delayed-contents.change', ({ target, detail }) => {
    const context = detail?.item || target;
    dispatchNavigationEditor(context);
  });

  /**
   * Banner Editor
   */
  dispatchBannerEditor(document);
  on('admin.delayed-contents.change', ({ target, detail }) => {
    const context = detail?.item || target;
    dispatchBannerEditor(context);
  });

  /**
   * メディア管理
   */
  dispatchMediaAdmin(document);
  on('admin.delayed-contents.change', ({ target, detail }) => {
    const context = detail?.item || target;
    dispatchMediaAdmin(context);
  });

  /**
   * メディアフィールド
   */
  dispatchMediaField(document);
  on('dialog.open.after', ({ element }) => {
    dispatchMediaField(element);
  });
  on('field-group.add.after', ({ element }) => {
    dispatchMediaField(element);
  });
  on('custom-field-maker.preview.after', ({ element }) => {
    dispatchMediaField(element);
  });

  /**
   * エントリー管理
   */
  dispatchEntryAdmin(document);

  /**
   * エントリー一括変更選択
   */
  dispatchEntryBulkChangeSelect(document);

  /**
   * モジュール管理
   */
  dispatchModuleAdmin(document);

  /**
   * ライセンス警告
   */
  setupLicenseToast(document);

  /**
   * GeoPicker
   */
  const dispatchGeoInput = (context) => {
    const buttons = context.querySelectorAll('.js-geo-button');
    buttons.forEach((button) => {
      const form = context.querySelector(button.dataset.target);
      if (!form) {
        return;
      }
      const lat = form.querySelector('[name=geo_lat]');
      const lng = form.querySelector('[name=geo_lng]');
      const zoom = form.querySelector('[name=geo_zoom]');

      const addGeo = function () {
        const {
          adminLocationDefaultLat: defaultLat,
          adminLocationDefaultLng: defaultLng,
          adminLocationDefaultZoom: defaultZoom = '10',
        } = ACMS.Config;
        if (lat.value === '') {
          lat.value = defaultLat;
          lat.dispatchEvent(new Event('change', { bubbles: true }));
        }
        if (lng.value === '') {
          lng.value = defaultLng;
          lng.dispatchEvent(new Event('change', { bubbles: true }));
        }
        if (zoom.value === '') {
          zoom.value = defaultZoom || '10';
          zoom.dispatchEvent(new Event('change', { bubbles: true }));
        }
        form.style.display = 'block';
        button.textContent = ACMS.i18n('geo.message1');
        button.setAttribute('data-type', 'add');
        void emit('geo.info-add.after', { element: form.parentElement });
        ACMS.dispatchEvent('onGeoInfoAdded', form.parentElement); // 旧イベントシステムとの互換性維持のため残す
      };

      const deleteGeo = function () {
        form.style.display = 'none';
        button.textContent = ACMS.i18n('geo.message2');
        button.setAttribute('data-type', 'delete');
        lat.value = '';
        lat.dispatchEvent(new Event('change', { bubbles: true }));
        lng.value = '';
        lng.dispatchEvent(new Event('change', { bubbles: true }));
        zoom.value = '';
        zoom.dispatchEvent(new Event('change', { bubbles: true }));
      };

      const handleClick = () => {
        const { type } = button.dataset;
        if (type === 'add') {
          deleteGeo();
        } else if (type === 'delete') {
          addGeo();
        }
      };

      button.addEventListener('click', handleClick);
      handleClick(); // Initial trigger
    });
  };
  dispatchGeoInput(document);
  on('dialog.open.after', ({ element }) => {
    dispatchGeoInput(element);
  });

  /**
   * 編集画面のラベル変更
   */
  const changeEntryLabels = () => {
    const EditorJson = document.getElementById('entry-labels');
    if (!EditorJson) {
      return;
    }
    try {
      const json = JSON.parse(EditorJson.innerHTML);
      Object.keys(json).forEach((item) => {
        const target = document.getElementById(item);
        if (target && json[item]) {
          target.innerHTML = json[item];
        }
      });
    } catch (e) {
      console.log('JSONのparseに失敗しました。');
    }
    const details = document.getElementById('js-entry-details');
    if (!details) {
      return;
    }
    const tableSelector = details.dataset.target;
    if (!tableSelector) {
      return;
    }
    const table = details.querySelector(tableSelector);
    if (!table) {
      return;
    }
    let flag = false;
    const trs = table.querySelectorAll('tbody > tr');
    [].forEach.call(trs, (tr) => {
      const style = window.getComputedStyle(tr);
      if (style && style.display !== 'none') {
        flag = true;
      }
    });
    if (!flag) {
      details.style.display = 'none';
    }
  };

  changeEntryLabels();
  on('admin.delayed-contents.change', changeEntryLabels);

  /**
   * Dispatch acmsAdminReady
   *
   * ACMS.Ready（ablogcms/acms.js）と同型の「一度きりの起動完了通知」。発火タイミングに関わらず
   * 取りこぼさず購読できるようにするため、readyState プロパティを先に立ててから発火する
   */
  ACMS.readyStates.adminReady = 'complete';
  void emit('acms.state.admin-ready');
  ACMS.dispatchEvent('acmsAdminReady'); // 旧イベントシステムとの互換性維持のため残す
});
