import { useEffect, useMemo, useState } from 'react';
import type { SelectOptionItem } from '../../components/inputs/select';

type LayoutComponentBinding = {
  optionId: string;
  label: string;
};

type LayoutLeafNode = {
  id: string;
  componentBinding: LayoutComponentBinding | null;
};

export type LayoutPreviewBindingKind = 'action-button' | 'base-input' | 'select-input' | 'text-memo-widget' | 'sample';

export type LayoutPreviewMemoNote = {
  id: string;
  body: string;
  updatedAt: string;
};

export type LayoutPreviewMemoState = {
  draftBody: string;
  selectedId: string | null;
  isListOpen: boolean;
  isLoading: boolean;
  notes: LayoutPreviewMemoNote[];
};

export type LayoutPreviewBaseInputState = {
  value: string;
};

export type LayoutPreviewSelectState = {
  selectedCode: string | undefined;
  selectedItem: SelectOptionItem | null;
};

export type LayoutPreviewEmptyPaneState = {
  readiness: 'unassigned' | 'drafting' | 'ready';
  note: string;
  updatedAt: string | null;
};

export type LayoutPreviewInteractionRule = {
  sourceLeafId: string;
  targetLeafId: string;
};

type LayoutPreviewRuntime = {
  memoStates: Record<string, LayoutPreviewMemoState>;
  baseInputStates: Record<string, LayoutPreviewBaseInputState>;
  selectStates: Record<string, LayoutPreviewSelectState>;
  emptyPaneStates: Record<string, LayoutPreviewEmptyPaneState>;
  setMemoDraftBody: (leafId: string, nextValue: string) => void;
  toggleMemoList: (leafId: string) => void;
  saveMemoDraft: (leafId: string) => void;
  startMemoDraft: (leafId: string) => void;
  selectMemoNote: (leafId: string, noteId: string) => void;
  moveMemoSelection: (leafId: string, direction: -1 | 1) => void;
  deleteMemoSelection: (leafId: string) => void;
  setSelectValue: (leafId: string, nextCode?: string, nextItem?: SelectOptionItem) => void;
  setEmptyPaneReadiness: (leafId: string, readiness: LayoutPreviewEmptyPaneState['readiness']) => void;
  setEmptyPaneNote: (leafId: string, nextValue: string) => void;
};

const EMPTY_MEMO_STATE: LayoutPreviewMemoState = {
  draftBody: '',
  selectedId: null,
  isListOpen: false,
  isLoading: false,
  notes: [],
};

const EMPTY_BASE_INPUT_STATE: LayoutPreviewBaseInputState = {
  value: '',
};

const EMPTY_SELECT_STATE: LayoutPreviewSelectState = {
  selectedCode: undefined,
  selectedItem: null,
};

const EMPTY_EMPTY_PANE_STATE: LayoutPreviewEmptyPaneState = {
  readiness: 'unassigned',
  note: '',
  updatedAt: null,
};

function createMemoNote(body: string): LayoutPreviewMemoNote {
  const timestamp = new Date().toISOString();

  return {
    id: `memo-preview-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
    body,
    updatedAt: timestamp,
  };
}

function extractMemoFirstLine(body: string) {
  return body
    .split(/\r?\n/u)
    .map((line) => line.trim())
    .find(Boolean) ?? '';
}

export function resolveLayoutPreviewBindingKind(binding: LayoutComponentBinding | null): LayoutPreviewBindingKind {
  if (!binding) {
    return 'sample';
  }

  if (
    binding.optionId.includes('button-editable-input') ||
    binding.label === 'Button Editable Input'
  ) {
    return 'action-button';
  }

  if (
    binding.optionId === 'component:input:deferred-input' ||
    binding.optionId === 'component:input:input-base' ||
    binding.label === 'Base Input'
  ) {
    return 'base-input';
  }

  if (
    binding.optionId === 'component:select-input:select-input-base' ||
    binding.optionId === 'component:select-input:select-input' ||
    binding.label === 'Select Input'
  ) {
    return 'select-input';
  }

  if (binding.optionId === 'widget:text-memo-widget:text-memo-widget' || binding.label === 'Text Memo Widget') {
    return 'text-memo-widget';
  }

  return 'sample';
}

function syncRecordKeys<T>(
  previous: Record<string, T>,
  nextKeys: string[],
  createValue: () => T,
) {
  const nextSet = new Set(nextKeys);
  let changed = false;
  const nextRecord: Record<string, T> = {};

  nextKeys.forEach((key) => {
    if (key in previous) {
      nextRecord[key] = previous[key];
      return;
    }

    nextRecord[key] = createValue();
    changed = true;
  });

  Object.keys(previous).forEach((key) => {
    if (!nextSet.has(key)) {
      changed = true;
    }
  });

  return changed ? nextRecord : previous;
}

export function useLayoutPreviewRuntime(
  leafNodes: LayoutLeafNode[],
  interactionRules: LayoutPreviewInteractionRule[],
): LayoutPreviewRuntime {
  const leafBindingKindMap = useMemo(
    () => new Map(leafNodes.map((leaf) => [leaf.id, resolveLayoutPreviewBindingKind(leaf.componentBinding)])),
    [leafNodes],
  );
  const baseInputLeafIds = useMemo(
    () =>
      leafNodes
        .filter((leaf) => resolveLayoutPreviewBindingKind(leaf.componentBinding) === 'base-input')
        .map((leaf) => leaf.id),
    [leafNodes],
  );
  const memoLeafIds = useMemo(
    () =>
      leafNodes
        .filter((leaf) => resolveLayoutPreviewBindingKind(leaf.componentBinding) === 'text-memo-widget')
        .map((leaf) => leaf.id),
    [leafNodes],
  );
  const selectLeafIds = useMemo(
    () =>
      leafNodes
        .filter((leaf) => resolveLayoutPreviewBindingKind(leaf.componentBinding) === 'select-input')
        .map((leaf) => leaf.id),
    [leafNodes],
  );
  const [memoStates, setMemoStates] = useState<Record<string, LayoutPreviewMemoState>>({});
  const [baseInputStates, setBaseInputStates] = useState<Record<string, LayoutPreviewBaseInputState>>({});
  const [selectStates, setSelectStates] = useState<Record<string, LayoutPreviewSelectState>>({});
  const [emptyPaneStates, setEmptyPaneStates] = useState<Record<string, LayoutPreviewEmptyPaneState>>({});

  const memoToBaseInputTargets = useMemo(() => {
    const nextMap = new Map<string, string[]>();

    interactionRules.forEach((rule) => {
      const sourceKind = leafBindingKindMap.get(rule.sourceLeafId);
      const targetKind = leafBindingKindMap.get(rule.targetLeafId);

      if (sourceKind !== 'text-memo-widget' || targetKind !== 'base-input') {
        return;
      }

      const nextTargets = nextMap.get(rule.sourceLeafId) ?? [];

      if (!nextTargets.includes(rule.targetLeafId)) {
        nextTargets.push(rule.targetLeafId);
      }

      nextMap.set(rule.sourceLeafId, nextTargets);
    });

    return nextMap;
  }, [interactionRules, leafBindingKindMap]);

  const syncBaseInputsFromMemo = (sourceLeafId: string, draftBody: string) => {
    const targetLeafIds = memoToBaseInputTargets.get(sourceLeafId) ?? [];

    if (!targetLeafIds.length) {
      return;
    }

    const nextValue = extractMemoFirstLine(draftBody);

    setBaseInputStates((previous) => {
      const nextState = { ...previous };

      targetLeafIds.forEach((leafId) => {
        nextState[leafId] = {
          ...(previous[leafId] ?? EMPTY_BASE_INPUT_STATE),
          value: nextValue,
        };
      });

      return nextState;
    });
  };

  useEffect(() => {
    setBaseInputStates((previous) => syncRecordKeys(previous, baseInputLeafIds, () => ({ ...EMPTY_BASE_INPUT_STATE })));
  }, [baseInputLeafIds]);

  useEffect(() => {
    setMemoStates((previous) => syncRecordKeys(previous, memoLeafIds, () => ({ ...EMPTY_MEMO_STATE })));
  }, [memoLeafIds]);

  useEffect(() => {
    setSelectStates((previous) => syncRecordKeys(previous, selectLeafIds, () => ({ ...EMPTY_SELECT_STATE })));
  }, [selectLeafIds]);

  useEffect(() => {
    const emptyPaneLeafIds = leafNodes.filter((leaf) => !leaf.componentBinding).map((leaf) => leaf.id);
    setEmptyPaneStates((previous) => syncRecordKeys(previous, emptyPaneLeafIds, () => ({ ...EMPTY_EMPTY_PANE_STATE })));
  }, [leafNodes]);

  const setMemoDraftBody = (leafId: string, nextValue: string) => {
    syncBaseInputsFromMemo(leafId, nextValue);
    setMemoStates((previous) => ({
      ...previous,
      [leafId]: {
        ...(previous[leafId] ?? EMPTY_MEMO_STATE),
        draftBody: nextValue,
      },
    }));
  };

  const toggleMemoList = (leafId: string) => {
    setMemoStates((previous) => ({
      ...previous,
      [leafId]: {
        ...(previous[leafId] ?? EMPTY_MEMO_STATE),
        isListOpen: !(previous[leafId]?.isListOpen ?? false),
      },
    }));
  };

  const saveMemoDraft = (leafId: string) => {
    const current = memoStates[leafId] ?? EMPTY_MEMO_STATE;
    const trimmedBody = current.draftBody.trim();

    if (!trimmedBody) {
      return;
    }

    syncBaseInputsFromMemo(leafId, trimmedBody);
    setMemoStates((previous) => {
      const nextCurrent = previous[leafId] ?? EMPTY_MEMO_STATE;
      const matchedNote = nextCurrent.selectedId ? nextCurrent.notes.find((note) => note.id === nextCurrent.selectedId) ?? null : null;
      const nextNote = matchedNote
        ? {
            ...matchedNote,
            body: trimmedBody,
            updatedAt: new Date().toISOString(),
          }
        : createMemoNote(trimmedBody);
      const filteredNotes = nextCurrent.notes.filter((note) => note.id !== nextNote.id);

      return {
        ...previous,
        [leafId]: {
          ...nextCurrent,
          draftBody: nextNote.body,
          selectedId: nextNote.id,
          isListOpen: false,
          notes: [nextNote, ...filteredNotes].slice(0, 12),
        },
      };
    });
  };

  const startMemoDraft = (leafId: string) => {
    syncBaseInputsFromMemo(leafId, '');
    setMemoStates((previous) => ({
      ...previous,
      [leafId]: {
        ...(previous[leafId] ?? EMPTY_MEMO_STATE),
        draftBody: '',
        selectedId: null,
        isListOpen: false,
      },
    }));
  };

  const selectMemoNote = (leafId: string, noteId: string) => {
    const current = memoStates[leafId] ?? EMPTY_MEMO_STATE;
    const selectedNote = current.notes.find((note) => note.id === noteId) ?? null;

    if (!selectedNote) {
      return;
    }

    syncBaseInputsFromMemo(leafId, selectedNote.body);
    setMemoStates((previous) => {
      const nextCurrent = previous[leafId] ?? EMPTY_MEMO_STATE;
      const nextSelectedNote = nextCurrent.notes.find((note) => note.id === noteId) ?? null;

      if (!nextSelectedNote) {
        return previous;
      }

      return {
        ...previous,
        [leafId]: {
          ...nextCurrent,
          selectedId: nextSelectedNote.id,
          draftBody: nextSelectedNote.body,
          isListOpen: false,
        },
      };
    });
  };

  const deleteMemoSelection = (leafId: string) => {
    const current = memoStates[leafId] ?? EMPTY_MEMO_STATE;

    if (!current.selectedId && !current.draftBody.trim()) {
      return;
    }

    if (!current.selectedId) {
      syncBaseInputsFromMemo(leafId, '');
    } else {
      const nextNotes = current.notes.filter((note) => note.id !== current.selectedId);
      const nextSelectedNote = nextNotes[0] ?? null;
      syncBaseInputsFromMemo(leafId, nextSelectedNote?.body ?? '');
    }

    setMemoStates((previous) => {
      const nextCurrent = previous[leafId] ?? EMPTY_MEMO_STATE;

      if (!nextCurrent.selectedId && !nextCurrent.draftBody.trim()) {
        return previous;
      }

      if (!nextCurrent.selectedId) {
        return {
          ...previous,
          [leafId]: {
            ...nextCurrent,
            draftBody: '',
          },
        };
      }

      const nextNotes = nextCurrent.notes.filter((note) => note.id !== nextCurrent.selectedId);
      const nextSelectedNote = nextNotes[0] ?? null;

      return {
        ...previous,
        [leafId]: {
          ...nextCurrent,
          notes: nextNotes,
          selectedId: nextSelectedNote?.id ?? null,
          draftBody: nextSelectedNote?.body ?? '',
        },
      };
    });
  };

  const moveMemoSelection = (leafId: string, direction: -1 | 1) => {
    setMemoStates((previous) => {
      const current = previous[leafId] ?? EMPTY_MEMO_STATE;

      if (!current.notes.length) {
        return previous;
      }

      const baseIndex = current.selectedId
        ? current.notes.findIndex((note) => note.id === current.selectedId)
        : 0;
      const safeBaseIndex = baseIndex >= 0 ? baseIndex : 0;
      const nextIndex = safeBaseIndex + direction;

      if (nextIndex < 0 || nextIndex >= current.notes.length) {
        return previous;
      }

      const nextSelectedNote = current.notes[nextIndex];
      syncBaseInputsFromMemo(leafId, nextSelectedNote.body);
      return {
        ...previous,
        [leafId]: {
          ...current,
          selectedId: nextSelectedNote.id,
          draftBody: nextSelectedNote.body,
          isListOpen: false,
        },
      };
    });
  };

  const setSelectValue = (leafId: string, nextCode?: string, nextItem?: SelectOptionItem) => {
    setSelectStates((previous) => ({
      ...previous,
      [leafId]: {
        selectedCode: nextCode,
        selectedItem: nextItem ?? null,
      },
    }));
  };

  const setEmptyPaneReadiness = (leafId: string, readiness: LayoutPreviewEmptyPaneState['readiness']) => {
    setEmptyPaneStates((previous) => ({
      ...previous,
      [leafId]: {
        ...(previous[leafId] ?? EMPTY_EMPTY_PANE_STATE),
        readiness,
        updatedAt: new Date().toISOString(),
      },
    }));
  };

  const setEmptyPaneNote = (leafId: string, nextValue: string) => {
    setEmptyPaneStates((previous) => ({
      ...previous,
      [leafId]: {
        ...(previous[leafId] ?? EMPTY_EMPTY_PANE_STATE),
        note: nextValue,
        updatedAt: nextValue.trim() ? new Date().toISOString() : previous[leafId]?.updatedAt ?? null,
      },
    }));
  };

  return {
    memoStates,
    baseInputStates,
    selectStates,
    emptyPaneStates,
    setMemoDraftBody,
    toggleMemoList,
    saveMemoDraft,
    startMemoDraft,
    selectMemoNote,
    moveMemoSelection,
    deleteMemoSelection,
    setSelectValue,
    setEmptyPaneReadiness,
    setEmptyPaneNote,
  };
}
