import {
  CheckOutlined,
  CopyOutlined,
  DeleteOutlined,
  EditOutlined,
  LeftOutlined,
  PlusOutlined,
  RightOutlined,
  UnorderedListOutlined,
} from '@ant-design/icons';
import { Button, Empty, Input, Modal, message } from 'antd';
import type { TextAreaRef } from 'antd/es/input/TextArea';
import { forwardRef, useEffect, useMemo, useRef, useState } from 'react';
import { copyText } from '../../app/main/mainChatPanel/chatUtils';
import { renderModalWithEnterConfirm } from '../../app/main/modalKeyboard';
import { WidgetShell } from '../core';
import type { WidgetHandle } from '../core';
import {
  createTextMemoNote,
  deleteTextMemoNote as deleteTextMemoNoteFromServer,
  fetchTextMemoNotes,
  importTextMemoNotes,
  updateTextMemoNote as updateTextMemoNoteFromServer,
  type TextMemoNoteRecord,
} from './textMemoApi';
import './TextMemoWidget.css';

const STORAGE_KEY = 'ai-code-app:text-memo-widget';
const MAX_NOTE_COUNT = 12;
const MAX_BODY_LENGTH = 1200;

type MemoNote = {
  id: string;
  body: string;
  createdAt: string;
  updatedAt: string;
};

function readStoredNotes() {
  if (typeof window === 'undefined') {
    return [];
  }

  try {
    const rawValue = window.localStorage.getItem(STORAGE_KEY);
    if (!rawValue) {
      return [];
    }

    const parsed = JSON.parse(rawValue);
    if (!Array.isArray(parsed)) {
      return [];
    }

    return parsed.filter((note): note is MemoNote => {
      return (
        typeof note === 'object' &&
        note !== null &&
        typeof note.id === 'string' &&
        typeof note.body === 'string' &&
        typeof note.createdAt === 'string'
      );
    }).map((note) => ({
      ...note,
      updatedAt: typeof note.updatedAt === 'string' ? note.updatedAt : note.createdAt,
    }));
  } catch {
    return [];
  }
}

function clearStoredNotes() {
  if (typeof window === 'undefined') {
    return;
  }

  window.localStorage.removeItem(STORAGE_KEY);
}

function toMemoNote(note: TextMemoNoteRecord): MemoNote {
  return {
    id: note.id,
    body: note.body,
    createdAt: note.createdAt,
    updatedAt: note.updatedAt,
  };
}

function formatMemoTimestamp(value: string) {
  return new Intl.DateTimeFormat('ko-KR', {
    month: 'short',
    day: 'numeric',
    hour: 'numeric',
    minute: '2-digit',
  }).format(new Date(value));
}

function getPreviewText(body: string) {
  const [firstLine = ''] = body.split(/\r?\n/, 1);
  const preview = firstLine.trim();
  return preview || '새 메모';
}

function restoreMemoShellScroll(target?: EventTarget | null) {
  if (typeof window === 'undefined') {
    return;
  }

  const anchor =
    target instanceof HTMLElement
      ? target.closest('.app-shell')
      : document.activeElement instanceof HTMLElement
        ? document.activeElement.closest('.app-shell')
        : document.querySelector('.app-shell');

  if (!(anchor instanceof HTMLElement) || anchor.scrollTop <= 0) {
    return;
  }

  anchor.scrollTop = 0;
}

export type TextMemoWidgetProps = {
  cardWrapper?: boolean;
  headerless?: boolean;
  skin?: 'flat' | 'note';
  title?: string;
};

export const TextMemoWidget = forwardRef<WidgetHandle, TextMemoWidgetProps>(function TextMemoWidget(
  { cardWrapper, headerless = false, skin = 'note', title = '메모 본문' },
  ref,
) {
  const [messageApi, contextHolder] = message.useMessage();
  const [modalApi, modalContextHolder] = Modal.useModal();
  const [body, setBody] = useState('');
  const [notes, setNotes] = useState<MemoNote[]>([]);
  const [selectedId, setSelectedId] = useState<string | null>(null);
  const [isListOpen, setIsListOpen] = useState(false);
  const [isEditing, setIsEditing] = useState(true);
  const [isLoading, setIsLoading] = useState(true);
  const [isSaving, setIsSaving] = useState(false);
  const textAreaRef = useRef<TextAreaRef | null>(null);

  useEffect(() => {
    let cancelled = false;

    void (async () => {
      setIsLoading(true);

      try {
        const serverNotes = await fetchTextMemoNotes();
        let nextNotes = serverNotes.map(toMemoNote);

        if (nextNotes.length === 0) {
          const storedNotes = readStoredNotes();

          if (storedNotes.length > 0) {
            const imported = await importTextMemoNotes(
              storedNotes.map((note) => ({
                id: note.id,
                body: note.body,
                createdAt: note.updatedAt || note.createdAt,
              })),
            );
            nextNotes = imported.map(toMemoNote);
            clearStoredNotes();
          }
        }

        if (cancelled) {
          return;
        }

        setNotes(nextNotes);

        if (nextNotes[0]) {
          setSelectedId(nextNotes[0].id);
          setBody(nextNotes[0].body);
          setIsEditing(false);
        } else {
          setSelectedId(null);
          setBody('');
          setIsEditing(true);
        }
      } catch (error) {
        if (cancelled) {
          return;
        }

        const nextMessage = error instanceof Error ? error.message : '메모를 불러오지 못했습니다.';
        void messageApi.error(nextMessage);
      } finally {
        if (!cancelled) {
          setIsLoading(false);
        }
      }
    })();

    return () => {
      cancelled = true;
    };
  }, []);

  const selectedIndex = useMemo(() => {
    if (!selectedId) {
      return -1;
    }

    return notes.findIndex((note) => note.id === selectedId);
  }, [notes, selectedId]);

  const hasDraft = body.trim().length > 0;
  const selectedNote = selectedIndex >= 0 ? notes[selectedIndex] : null;
  const isDirty = selectedNote ? selectedNote.body !== body : hasDraft;
  const isFlat = skin === 'flat';

  const saveNote = async () => {
    const trimmedBody = body.trim();
    if (!trimmedBody || isSaving) {
      return;
    }

    setIsSaving(true);

    try {
      if (selectedNote) {
        const updatedNote = toMemoNote(await updateTextMemoNoteFromServer(selectedNote.id, { body: trimmedBody }));
        const nextNotes = [updatedNote, ...notes.filter((note) => note.id !== selectedNote.id)].slice(
          0,
          MAX_NOTE_COUNT,
        );

        setNotes(nextNotes);
        setSelectedId(updatedNote.id);
        setBody(updatedNote.body);
        setIsEditing(false);
        void messageApi.success('저장됨');
        return;
      }

      const nextNote = toMemoNote(await createTextMemoNote({ body: trimmedBody }));
      const nextNotes = [nextNote, ...notes].slice(0, MAX_NOTE_COUNT);

      setNotes(nextNotes);
      setSelectedId(nextNote.id);
      setBody(nextNote.body);
      setIsEditing(false);
      void messageApi.success('저장됨');
    } catch (error) {
      const nextMessage = error instanceof Error ? error.message : '메모 저장에 실패했습니다.';
      void messageApi.error(nextMessage);
    } finally {
      setIsSaving(false);
    }
  };

  const handlePrimaryAction = () => {
    if (isEditing || isDirty || !selectedNote) {
      void saveNote();
      return;
    }

    setIsEditing(true);
  };

  const handleNew = () => {
    setSelectedId(null);
    setBody('');
    setIsEditing(true);
  };

  const handleDelete = () => {
    if (!selectedNote && !hasDraft) {
      return;
    }

    const isDraftOnly = !selectedNote;

    void modalApi.confirm({
      title: isDraftOnly ? '작성 중인 메모를 삭제할까요?' : '선택한 메모를 삭제할까요?',
      content: isDraftOnly
        ? '저장하지 않은 내용이 사라집니다.'
        : '삭제한 메모는 다시 복구할 수 없습니다.',
      okText: '삭제',
      cancelText: '취소',
      autoFocusButton: 'ok',
      modalRender: renderModalWithEnterConfirm,
      okButtonProps: { danger: true },
      onOk: async () => {
        if (isDraftOnly) {
          setBody('');
          setIsEditing(true);
          void messageApi.success('메모를 삭제했습니다.');
          return;
        }

        try {
          await deleteTextMemoNoteFromServer(selectedNote.id);
        } catch (error) {
          const nextMessage = error instanceof Error ? error.message : '메모 삭제에 실패했습니다.';
          void messageApi.error(nextMessage);
          return;
        }

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

        setNotes(nextNotes);
        setSelectedId(nextSelectedNote?.id ?? null);
        setBody(nextSelectedNote?.body ?? '');
        setIsEditing(nextSelectedNote ? false : true);
        setIsListOpen(nextNotes.length > 0 && isListOpen);
        void messageApi.success('메모를 삭제했습니다.');
      },
    });
  };

  const handleCopy = async () => {
    const trimmedBody = body.trim();
    if (!trimmedBody) {
      return;
    }

    try {
      await copyText(trimmedBody);
      void messageApi.success('복사했습니다.');
    } catch (error) {
      void messageApi.error(error instanceof Error ? error.message : '복사에 실패했습니다.');
    }
  };

  const handleSelect = (note: MemoNote) => {
    setSelectedId(note.id);
    setBody(note.body);
    setIsEditing(false);
    setIsListOpen(false);
  };

  const moveSelection = (direction: -1 | 1) => {
    if (notes.length === 0) {
      return;
    }

    const baseIndex = selectedIndex >= 0 ? selectedIndex : 0;
    const nextIndex = baseIndex + direction;

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

    const nextNote = notes[nextIndex];
    setSelectedId(nextNote.id);
    setBody(nextNote.body);
    setIsEditing(false);
  };

  return (
    <WidgetShell ref={ref} id="text-memo-widget" title="" cardWrapper={cardWrapper}>
      {contextHolder}
      {modalContextHolder}
      <div className={`text-memo-widget${isFlat ? ' text-memo-widget--flat' : ''}`}>
        {isFlat && !headerless ? (
          <div className="text-memo-widget__head">
            <span className="text-memo-widget__head-title">{title}</span>
            <span className="text-memo-widget__head-meta">{selectedNote ? '선택 항목 편집' : '새 항목 작성'}</span>
          </div>
        ) : null}
        <div className="text-memo-widget__toolbar" role="toolbar" aria-label="메모 도구">
          <div className="text-memo-widget__toolbar-group">
            <Button
              type="text"
              shape="circle"
              icon={<PlusOutlined />}
              aria-label="새 메모"
              onClick={handleNew}
            />
            <Button
              type="text"
              shape="circle"
              icon={<DeleteOutlined />}
              aria-label="메모 삭제"
              disabled={isLoading || isSaving || (!selectedNote && !hasDraft)}
              onClick={handleDelete}
            />
            <Button
              type="text"
              shape="circle"
              icon={<CopyOutlined />}
              aria-label="메모 복사"
              disabled={isLoading || !hasDraft}
              onClick={() => {
                void handleCopy();
              }}
            />
            <Button
              type="text"
              shape="circle"
              icon={<UnorderedListOutlined />}
              aria-label="메모 목록"
              disabled={isLoading}
              onClick={() => {
                setIsListOpen((previous) => !previous);
              }}
            />
          </div>

          <div className="text-memo-widget__toolbar-group">
            <Button
              type="text"
              shape="circle"
              icon={<LeftOutlined />}
              aria-label="이전 메모"
              disabled={isLoading || selectedIndex <= 0}
              onClick={() => {
                moveSelection(-1);
              }}
            />
            <Button
              type="text"
              shape="circle"
              icon={<RightOutlined />}
              aria-label="다음 메모"
              disabled={isLoading || selectedIndex < 0 || selectedIndex >= notes.length - 1}
              onClick={() => {
                moveSelection(1);
              }}
            />
            <Button
              type="text"
              shape="circle"
              icon={isEditing || isDirty || !selectedNote ? <CheckOutlined /> : <EditOutlined />}
              aria-label={isEditing || isDirty || !selectedNote ? '저장' : '편집'}
              disabled={isLoading || isSaving || (!hasDraft && !selectedNote)}
              onClick={handlePrimaryAction}
            />
          </div>
        </div>

        <div className={`text-memo-widget__body${isListOpen ? ' text-memo-widget__body--list-open' : ''}`}>
          {isLoading ? (
            <div className="text-memo-widget__empty">
              <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="메모를 불러오는 중입니다." />
            </div>
          ) : isListOpen ? (
            <div className="text-memo-widget__sheet">
              {notes.length === 0 ? (
                <div className="text-memo-widget__empty">
                  <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={false} />
                </div>
              ) : (
                <div className="text-memo-widget__list">
                  {notes.map((note) => (
                    <button
                      key={note.id}
                      type="button"
                      className={`text-memo-widget__list-item${
                        note.id === selectedId ? ' text-memo-widget__list-item--active' : ''
                      }`}
                      onClick={() => {
                        handleSelect(note);
                      }}
                    >
                      <span className="text-memo-widget__list-time">{formatMemoTimestamp(note.createdAt)}</span>
                      <span className="text-memo-widget__list-preview">{getPreviewText(note.body)}</span>
                    </button>
                  ))}
                </div>
              )}
            </div>
          ) : (
            <div className="text-memo-widget__editor">
              <div className="text-memo-widget__meta">
                <span>{selectedNote ? formatMemoTimestamp(selectedNote.updatedAt) : ''}</span>
                <span>{body.length}/{MAX_BODY_LENGTH}</span>
              </div>

              <Input.TextArea
                ref={textAreaRef}
                className="text-memo-widget__input"
                value={body}
                placeholder="메모 입력"
                variant="borderless"
                maxLength={MAX_BODY_LENGTH}
                readOnly={isSaving || (!isEditing && !!selectedNote)}
                onChange={(event) => {
                  setBody(event.target.value);
                  window.requestAnimationFrame(() => {
                    restoreMemoShellScroll(event.target);
                  });
                }}
                onFocus={() => {
                  if (!selectedNote) {
                    setIsEditing(true);
                  }
                  window.requestAnimationFrame(() => {
                    restoreMemoShellScroll();
                  });
                }}
              />
            </div>
          )}
        </div>
      </div>
    </WidgetShell>
  );
});
