import { defineStore } from 'pinia';
import { getNotesApi, type Note, type NoteStatus, type PinPosition } from '../api/notes';
import { mergeNotes, repliesOf, threadsOf } from '../notes/model';
import { useDocumentStore } from './document';
import { useEditorStore } from './editor';
import { t } from '../core/i18n';

export type NotesFilter = 'open' | 'resolved';

export interface NoteDraft {
  elementId: string;
  position: PinPosition;
}

let timer: ReturnType<typeof setInterval> | null = null;

/**
 * Notes & comments (T169). Notes mode turns canvas clicks into pins (`notes/NotesHost.vue`); threads open in a
 * popover (`notes/NoteThread.vue`) and are listed in the Notes panel. The list is polled every
 * `config.notes.pollInterval` seconds (30) while the editor is open; `mergeNotes` folds the result in.
 */
export const useNotesStore = defineStore('notes', {
  state: () => ({
    mode: false,
    notes: [] as Note[],
    loaded: false,
    error: '',
    filter: 'open' as NotesFilter,
    /** Thread root shown in the popover. */
    activeId: null as number | null,
    /** A new pin waiting for its first message. */
    draft: null as NoteDraft | null,
    busy: false,
  }),

  getters: {
    threads: (s): Note[] => threadsOf(s.notes),
    openCount(): number {
      return this.threads.filter((n) => n.status === 'open').length;
    },
    visibleThreads(): Note[] {
      return this.threads.filter((n) => n.status === this.filter);
    },
    active(): Note | null {
      return this.activeId === null ? null : (this.notes.find((n) => n.id === this.activeId) ?? null);
    },
    /** Pins drawn on the canvas: open threads, plus the active one even when resolved. */
    pins(): Note[] {
      return this.threads.filter((n) => n.status === 'open' || n.id === this.activeId);
    },
  },

  actions: {
    replies(rootId: number): Note[] {
      return repliesOf(this.notes, rootId);
    },
    /** Folds a server list in (used by load + polling). */
    merge(remote: Note[]): boolean {
      const { notes, changed } = mergeNotes(this.notes, remote);
      if (changed) this.notes = notes;
      if (this.activeId !== null && !notes.some((n) => n.id === this.activeId)) this.activeId = null;
      return changed;
    },
    async load(): Promise<void> {
      const postId = useDocumentStore().postId;
      if (!postId) return;
      try {
        const api = await getNotesApi();
        this.merge(await api.list(postId));
        this.loaded = true;
        this.error = '';
      } catch (e) {
        this.error = e instanceof Error ? e.message : String(e);
      }
    },
    startPolling(seconds = 30): void {
      this.stopPolling();
      const ms = Math.max(5, seconds) * 1000;
      timer = setInterval(() => {
        if (typeof document !== 'undefined' && document.hidden) return;
        void this.load();
      }, ms);
    },
    stopPolling(): void {
      if (timer) clearInterval(timer);
      timer = null;
    },
    setMode(on: boolean): void {
      const editor = useEditorStore();
      this.mode = on;
      if (on) {
        if (editor.inlineEditingId) editor.inlineEditingId = null;
        if (editor.selectedId) editor.select(null);
        editor.setPanel('notes');
      } else {
        this.draft = null;
        this.activeId = null;
        if (editor.panel === 'notes') editor.setPanel('widgets');
      }
    },
    toggleMode(): void {
      this.setMode(!this.mode);
    },
    startDraft(elementId: string, position: PinPosition): void {
      this.activeId = null;
      this.draft = { elementId, position };
    },
    open(id: number): void {
      const note = this.notes.find((n) => n.id === id);
      if (!note) return;
      this.draft = null;
      this.activeId = note.parentId || note.id;
    },
    close(): void {
      this.activeId = null;
      this.draft = null;
    },
    /** Deep link / panel jump: notes mode on, the thread open. Returns the root, or null when it does not exist. */
    focus(id: number): Note | null {
      const note = this.notes.find((n) => n.id === id);
      if (!note) return null;
      if (!this.mode) this.setMode(true);
      const root = this.notes.find((n) => n.id === (note.parentId || note.id)) ?? note;
      if (root.status === 'resolved') this.filter = 'resolved';
      this.open(root.id);
      return root;
    },
    async run<T>(fn: () => Promise<T>): Promise<T | null> {
      this.busy = true;
      try {
        return await fn();
      } catch (e) {
        useEditorStore().notify(e instanceof Error && e.message ? e.message : t('The note could not be saved.'), 'error');
        return null;
      } finally {
        this.busy = false;
      }
    },
    async createThread(content: string): Promise<Note | null> {
      const draft = this.draft;
      if (!draft || !content.trim()) return null;
      const note = await this.run(async () => (await getNotesApi()).create({ postId: useDocumentStore().postId, content, elementId: draft.elementId, position: { x: draft.position.x, y: draft.position.y } }));
      if (!note) return null;
      this.notes = [...this.notes, note];
      this.draft = null;
      this.activeId = note.id;
      this.filter = 'open';
      return note;
    },
    async reply(rootId: number, content: string): Promise<Note | null> {
      if (!content.trim()) return null;
      const note = await this.run(async () => (await getNotesApi()).create({ postId: useDocumentStore().postId, content, parentId: rootId }));
      if (note) this.notes = [...this.notes, note];
      return note;
    },
    async edit(id: number, content: string): Promise<Note | null> {
      const note = await this.run(async () => (await getNotesApi()).update(id, { content }));
      if (note) this.notes = this.notes.map((n) => (n.id === id ? note : n));
      return note;
    },
    async setStatus(id: number, status: NoteStatus): Promise<Note | null> {
      const note = await this.run(async () => (await getNotesApi()).update(id, { status }));
      if (note) this.notes = this.notes.map((n) => (n.id === id ? note : n));
      return note;
    },
    async remove(id: number): Promise<boolean> {
      const ids = await this.run(async () => (await getNotesApi()).remove(id));
      if (!ids) return false;
      this.notes = this.notes.filter((n) => !ids.includes(n.id));
      if (this.activeId !== null && ids.includes(this.activeId)) this.activeId = null;
      return true;
    },
  },
});
