import {
  defineComponent,
  provide,
  shallowReactive,
  shallowRef,
  ref,
  watch,
  computed,
  Transition,
  PropType,
  Ref,
} from 'vue';
import {
  IBlockData,
  IBlockDefinition,
  IBlockLibrary,
  ITreeNode,
  OnUpdateBlockCb,
  IFormattingTool,
  IFormattingToolLibrary,
} from '../types';
import { model } from '../block-helpers';
import { SymMode, SbMode } from '../mode';
import { SymBlockLibrary} from '../use-dynamic-blocks';
import {
  SymBlockTree,
  SymBlockTreeRegister,
  SymBlockTreeUnregister,
} from '../use-block-tree';
import { SymEditorDimensions, useResizeObserver } from '../use-resize-observer';
import { SymActiveBlock } from '../use-activation';
import { SymRootElement } from '../use-root-element';
import {
  SbFormattingToolbar,
  SymFormattingToolLibrary,
  SymFormattingToolbarClients,
  SymFormattingToolbarActiveClient,
} from '../rich-text';

import { SbMainMenu } from './MainMenu';
import { SbBlock } from './Block';

export interface ISbMainProps {
  availableBlocks: IBlockDefinition<any>[];
  availableFormattingTools: IFormattingTool[];
  block: IBlockData<any>;
  eventUpdate: OnUpdateBlockCb;
  mode: SbMode;
}

import './Main.scss';

export const SbMain = defineComponent({
  name: 'sb-main',

  model,

  props: {
    availableBlocks: {
      type: Array as PropType<IBlockDefinition<any>[]>,
      default: () => [],
    },
    availableFormattingTools: {
      type: Array as PropType<IFormattingTool[]>,
      default: () => [],
    },
    block: {
      type: Object as PropType<IBlockData<any>>,
      required: true,
    },
    /**
     * Called when the block should be updated.
     */
    eventUpdate: {
      type: (null as unknown) as PropType<OnUpdateBlockCb>,
      default: () => {},
    },
    mode: {
      type: String as PropType<SbMode>,
      validator(value: any) {
        return Object.values(SbMode).includes(value);
      },
      default: SbMode.Edit,
    },
  },

  setup(props: ISbMainProps) {
    const el: Ref<null|HTMLElement> = ref(null);
    useResizeObserver(el, SymEditorDimensions);
    provide(SymRootElement, el);

    const inlineClients = shallowRef([]);
    provide(SymFormattingToolbarClients, inlineClients);

    provide(SymFormattingToolbarActiveClient, ref(null));

    const mode = ref(props.mode);
    provide(SymMode, mode);

    watch(() => props.mode, (newMode) => {
      mode.value = newMode;
    });

    const classes = computed(() => ({
      'sb-main': true,
      [`sb-main_${mode.value}`]: true,
    }));

    const activeBlock = ref(null);
    provide(SymActiveBlock, activeBlock);

    const blockTree: Ref<ITreeNode|null> = ref(null);
    provide(SymBlockTree, blockTree);
    provide(SymBlockTreeRegister, (block: ITreeNode) => { blockTree.value = block; });
    provide(SymBlockTreeUnregister, () => { blockTree.value = null; });

    const blockLibrary: IBlockLibrary = shallowReactive({
      ...props.availableBlocks.reduce(
        (blocks: IBlockLibrary, block: IBlockDefinition<any>) => ({ ...blocks, [block.name]: block }),
        {},
      ),
    });
    provide(SymBlockLibrary, blockLibrary);

    const inlineToolLibrary: IFormattingToolLibrary = shallowReactive({
      ...props.availableFormattingTools.reduce(
        (tools: IFormattingToolLibrary, tool: IFormattingTool) => ({ ...tools, [tool.name]: tool }),
        {},
      ),
    });
    provide(SymFormattingToolLibrary, inlineToolLibrary);

    return () => (
      <div
        class={classes.value}
        ref={el}
      >
        <Transition
          name="sb-main--menu"
          mode="out-in"
        >
          {mode.value === SbMode.Edit
            ? <SbMainMenu
              block={props.block}
              class="sb-main--menu"
            />
            : null}
        </Transition>
        <SbBlock
          class="sb-main--block"
          block={props.block}
          eventUpdate={props.eventUpdate}
        />
        <SbFormattingToolbar />
      </div>
    );
  },
});
