"use client";

import { useCallback, useEffect, useState } from "react";
import { createClient, getBrowserUserId } from "@/lib/supabase/client";
import { censorText, censorTiptapContent } from "@/lib/content-filter";
import type { Note, NoteFolder, Tag } from "@/types";
import { logProjectActivity } from "@/lib/project-activity";
import { logTeamAudit } from "@/lib/team-audit";
import { useWorkspaceStore } from "@/stores/workspace-store";
import { emitWebhookEvent } from "@/lib/developers/client";

export interface NoteWithRelations extends Note {
  tags: Tag[];
}

const EMPTY_NOTE_CONTENT = {
  type: "doc",
  content: [{ type: "paragraph", content: [] }],
};

export function useNotes() {
  const [notes, setNotes] = useState<NoteWithRelations[]>([]);
  const [folders, setFolders] = useState<NoteFolder[]>([]);
  const [loading, setLoading] = useState(true);
  const [userId, setUserId] = useState<string | null>(null);
  const supabase = createClient();
  const activeTeamId = useWorkspaceStore((state) => state.activeTeamId);
  const initialized = useWorkspaceStore((state) => state.initialized);

  useEffect(() => {
    void getBrowserUserId().then(setUserId).catch(() => setUserId(null));
  }, []);

  async function requireCurrentUserId() {
    const currentUserId = userId || (await getBrowserUserId());
    if (currentUserId && currentUserId !== userId) {
      setUserId(currentUserId);
    }
    return currentUserId;
  }

  const fetchNotes = useCallback(async () => {
    if (!initialized) return;

    let query = supabase
      .from("notes")
      .select("*, note_tags(tag_id, tags:tags(*))")
      .order("is_pinned", { ascending: false })
      .order("pinned_order")
      .order("updated_at", { ascending: false });

    query = activeTeamId
      ? query.eq("team_id", activeTeamId)
      : query.is("team_id", null);

    const { data } = await query;

    const result: NoteWithRelations[] = (data || []).map((n) => ({
      ...n,
      tags:
        (n.note_tags as Array<{ tags: Tag }>)
          ?.map((nt) => nt.tags)
          .filter(Boolean) || [],
    }));

    setNotes(result);
  }, [activeTeamId, initialized, supabase]);

  const fetchFolders = useCallback(async () => {
    if (!initialized) return;

    let query = supabase
      .from("note_folders")
      .select("*")
      .order("sort_order")
      .order("name");

    query = activeTeamId
      ? query.eq("team_id", activeTeamId)
      : query.is("team_id", null);

    const { data } = await query;
    setFolders(data || []);
  }, [activeTeamId, initialized, supabase]);

  useEffect(() => {
    if (!initialized) return;

    async function loadNotesData() {
      setLoading(true);
      await Promise.all([fetchNotes(), fetchFolders()]);
      setLoading(false);
    }
    void loadNotesData();
  }, [fetchFolders, fetchNotes, initialized]);

  async function createNote(input: {
    title?: string;
    content?: Record<string, unknown>;
    folder_id?: string | null;
    project_id?: string | null;
    parent_note_id?: string | null;
    cover_id?: string | null;
    // Set when this note is created from a template — lets "Create template"
    // refuse to re-publish a note that came from a PAID template, so paid
    // templates can't be resold for free or cheaper.
    source_template_id?: string | null;
    source_template_price_cents?: number;
  }) {
    const currentUserId = await requireCurrentUserId();
    if (!currentUserId) throw new Error("Not authenticated");

    const { data, error } = await supabase
      .from("notes")
      .insert({
        user_id: currentUserId,
        title: input.title ? censorText(input.title) : "Untitled",
        content: input.content ? censorTiptapContent(input.content) : EMPTY_NOTE_CONTENT,
        folder_id: input.folder_id || null,
        project_id: input.project_id || null,
        parent_note_id: input.parent_note_id || null,
        cover_id: input.cover_id || null,
        source_template_id: input.source_template_id || null,
        source_template_price_cents: input.source_template_price_cents || 0,
        team_id: activeTeamId,
      })
      .select()
      .single();
    if (error) throw error;
    await logProjectActivity(supabase, {
      userId: currentUserId,
      projectId: data.project_id,
      entityType: "note",
      entityId: data.id,
      action: "note_created",
      title: data.title,
    });
    await logTeamAudit(supabase, {
      teamId: activeTeamId,
      actorId: currentUserId,
      action: "note_created",
      entityType: "note",
      entityId: data.id,
      title: data.title,
    });
    void emitWebhookEvent({
      teamId: activeTeamId,
      eventType: "note.created",
      data: {
        note: data,
      },
    });
    await fetchNotes();
    return data as Note;
  }

  async function updateNote(
    id: string,
    input: Partial<{
      title: string;
      content: Record<string, unknown>;
      is_pinned: boolean;
      pinned_order: number;
      folder_id: string | null;
      project_id: string | null;
      linked_task_id: string | null;
      tag_ids: string[];
    }>
  ) {
    const { tag_ids, ...rawNoteInput } = input;
    const noteInput = {
      ...rawNoteInput,
      ...(rawNoteInput.title !== undefined ? { title: censorText(rawNoteInput.title) } : {}),
      ...(rawNoteInput.content !== undefined
        ? { content: censorTiptapContent(rawNoteInput.content) }
        : {}),
    };

    const existing = notes.find((note) => note.id === id);
    const currentUserId = await requireCurrentUserId();

    const { error } = await supabase
      .from("notes")
      .update(noteInput)
      .eq("id", id);
    if (error) throw error;

    if (tag_ids !== undefined) {
      await supabase.from("note_tags").delete().eq("note_id", id);
      if (tag_ids.length > 0) {
        await supabase
          .from("note_tags")
          .insert(tag_ids.map((tag_id) => ({ note_id: id, tag_id })));
      }
    }

    await logProjectActivity(supabase, {
      userId: currentUserId,
      projectId:
        "project_id" in noteInput
          ? (noteInput.project_id as string | null | undefined)
          : existing?.project_id,
      entityType: "note",
      entityId: id,
      action: "note_updated",
      title: (noteInput.title as string | undefined) || existing?.title || "Note updated",
    });
    await logTeamAudit(supabase, {
      teamId: activeTeamId,
      actorId: currentUserId,
      action: "note_updated",
      entityType: "note",
      entityId: id,
      title: (noteInput.title as string | undefined) || existing?.title || "Note updated",
    });
    const { data: nextNote } = await supabase.from("notes").select("*").eq("id", id).single();
    void emitWebhookEvent({
      teamId: activeTeamId,
      eventType: "note.updated",
      data: {
        note: nextNote || null,
        previous_note: existing || null,
      },
    });

    await fetchNotes();
  }

  async function deleteNote(id: string) {
    const existing = notes.find((note) => note.id === id);
    const currentUserId = await requireCurrentUserId();
    const { error } = await supabase.from("notes").delete().eq("id", id);
    if (error) throw error;
    await logProjectActivity(supabase, {
      userId: currentUserId,
      projectId: existing?.project_id,
      entityType: "note",
      entityId: id,
      action: "note_deleted",
      title: existing?.title || "Note deleted",
    });
    await logTeamAudit(supabase, {
      teamId: activeTeamId,
      actorId: currentUserId,
      action: "note_deleted",
      entityType: "note",
      entityId: id,
      title: existing?.title || "Note deleted",
    });
    void emitWebhookEvent({
      teamId: activeTeamId,
      eventType: "note.deleted",
      data: {
        note_id: id,
        note: existing || null,
      },
    });
    await fetchNotes();
  }

  async function togglePin(id: string, currentPinned: boolean) {
    const existing = notes.find((note) => note.id === id);
    const nextPinned = !currentPinned;
    await updateNote(id, {
      is_pinned: nextPinned,
      pinned_order: nextPinned ? 0 : existing?.pinned_order ?? 0,
    });
    await logProjectActivity(supabase, {
      userId: await requireCurrentUserId(),
      projectId: existing?.project_id,
      entityType: "note",
      entityId: id,
      action: nextPinned ? "note_pinned" : "note_unpinned",
      title: existing?.title || "Note updated",
    });
  }

  async function reorderPinnedNotes(nextPinnedNotes: NoteWithRelations[]) {
    const currentUserId = await requireCurrentUserId();
    await Promise.all(
      nextPinnedNotes.map((note, index) =>
        supabase.from("notes").update({ pinned_order: index }).eq("id", note.id)
      )
    );
    setNotes((prev) =>
      prev.map((note) => {
        const pinnedIndex = nextPinnedNotes.findIndex((item) => item.id === note.id);
        return pinnedIndex >= 0 ? { ...note, pinned_order: pinnedIndex } : note;
      })
    );

    const projectId = nextPinnedNotes[0]?.project_id ?? null;
    await logProjectActivity(supabase, {
      userId: currentUserId,
      projectId,
      entityType: "note",
      action: "pinned_notes_reordered",
      title: "Pinned notes reordered",
    });
  }

  // Folders
  async function createFolder(name: string) {
    const currentUserId = await requireCurrentUserId();
    if (!currentUserId) throw new Error("Not authenticated");

    const { data, error } = await supabase
      .from("note_folders")
      .insert({ name, user_id: currentUserId, team_id: activeTeamId })
      .select()
      .single();
    if (error) throw error;
    setFolders((prev) => [...prev, data]);
    return data as NoteFolder;
  }

  async function renameFolder(id: string, name: string) {
    const { error } = await supabase
      .from("note_folders")
      .update({ name })
      .eq("id", id);
    if (error) throw error;
    setFolders((prev) => prev.map((f) => (f.id === id ? { ...f, name } : f)));
  }

  async function deleteFolder(id: string) {
    // Move notes in this folder to no folder
    await supabase
      .from("notes")
      .update({ folder_id: null })
      .eq("folder_id", id);
    const { error } = await supabase
      .from("note_folders")
      .delete()
      .eq("id", id);
    if (error) throw error;
    setFolders((prev) => prev.filter((f) => f.id !== id));
    await fetchNotes();
  }

  return {
    notes,
    folders,
    loading,
    createNote,
    updateNote,
    deleteNote,
    togglePin,
    reorderPinnedNotes,
    createFolder,
    renameFolder,
    deleteFolder,
    refetch: fetchNotes,
  };
}
