'use client';

import { useEffect, useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { formatDistanceToNow } from 'date-fns';
import { Lock, Pencil, Trash2, Send, Loader2 } from 'lucide-react';
import { useAutoAnimate } from '@formkit/auto-animate/react';

import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { apiFetch } from '@/lib/api/client';
import { stageLabel } from '@/lib/constants';

type NoteSource =
  | 'client'
  | 'interest'
  | 'yacht'
  | 'company'
  | 'residential_client'
  | 'residential_interest';

interface Note {
  id: string;
  content: string;
  authorId: string;
  authorName?: string;
  isLocked: boolean;
  createdAt: string;
  updatedAt: string;
  /** Aggregated-mode only: which child entity this note came from. */
  source?: NoteSource;
  sourceId?: string;
  sourceLabel?: string;
  /** Pipeline stage the linked interest was at when the note was authored.
   *  Only populated for interest notes — drives the small stage chip. */
  pipelineStageAtCreation?: string | null;
}

type NotesEntityType =
  | 'clients'
  | 'interests'
  | 'yachts'
  | 'companies'
  | 'residential_clients'
  | 'residential_interests';

/** Maps the entity-type the list is rendered for to the `source` value
 *  the aggregator uses when a note came from THAT entity itself
 *  (vs. a related entity). Used to decide whether a note is editable
 *  in-place or read-only with an "Open source" affordance. */
const SELF_SOURCE: Record<NotesEntityType, NoteSource | null> = {
  clients: 'client',
  yachts: 'yacht',
  companies: 'company',
  residential_clients: 'residential_client',
  // Aggregate-mode is only meaningful for the entities above. Interests
  // and residential_interests are leaf nodes — there's nothing to roll
  // up to them.
  interests: null,
  residential_interests: null,
};

const AGGREGATABLE: ReadonlySet<NotesEntityType> = new Set<NotesEntityType>([
  'clients',
  'yachts',
  'companies',
  'residential_clients',
]);

const SOURCE_BADGE_CLASS: Record<NoteSource, string> = {
  client: 'bg-violet-100 text-violet-900',
  interest: 'bg-blue-100 text-blue-900',
  yacht: 'bg-emerald-100 text-emerald-900',
  company: 'bg-amber-100 text-amber-900',
  residential_client: 'bg-violet-100 text-violet-900',
  residential_interest: 'bg-blue-100 text-blue-900',
};

const SOURCE_LABEL: Record<NoteSource, string> = {
  client: 'Client',
  interest: 'Interest',
  yacht: 'Yacht',
  company: 'Company',
  residential_client: 'Resident',
  residential_interest: 'Inquiry',
};

interface NotesListProps {
  entityType: NotesEntityType;
  entityId: string;
  currentUserId?: string;
  /**
   * Aggregate-on-read: union the entity's own notes with notes from
   * related entities (interests, owned yachts / company yachts, owner
   * client). Cross-source notes render with a source chip and are
   * read-only here — open the source entity's page to edit.
   *
   * Supported for entityType in {clients, yachts, companies,
   * residential_clients}. Ignored for interests / residential_interests.
   */
  aggregate?: boolean;
}

const NOTE_EDIT_WINDOW_MS = 15 * 60 * 1000; // 15 minutes

/** Sort by source then chronologically inside each source.
 *  Used by the aggregated view's "Group by source" toggle. */
function sortByGroup(notes: Note[]): Note[] {
  const sourceOrder: Record<string, number> = {
    client: 0,
    company: 1,
    yacht: 2,
    interest: 3,
    residential_client: 0,
    residential_interest: 1,
  };
  return [...notes].sort((a, b) => {
    const aRank = sourceOrder[a.source ?? ''] ?? 99;
    const bRank = sourceOrder[b.source ?? ''] ?? 99;
    if (aRank !== bRank) return aRank - bRank;
    const aLabel = a.sourceLabel ?? '';
    const bLabel = b.sourceLabel ?? '';
    if (aLabel !== bLabel) return aLabel.localeCompare(bLabel);
    return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
  });
}

export function NotesList({ entityType, entityId, currentUserId, aggregate }: NotesListProps) {
  const queryClient = useQueryClient();
  const [newNote, setNewNote] = useState('');
  const [editingId, setEditingId] = useState<string | null>(null);
  const [editContent, setEditContent] = useState('');
  const [groupBySource, setGroupBySource] = useState(false);
  // Wall-clock 'now' ticked every 30s so the per-note "Xm left to edit"
  // countdown decrements on screen. Reading `Date.now()` directly inside
  // render is impure (different value every call); pinning to a state
  // value means React Compiler can memoize cleanly.
  const [now, setNow] = useState(() => Date.now());
  useEffect(() => {
    const id = setInterval(() => setNow(Date.now()), 30_000);
    return () => clearInterval(id);
  }, []);

  const aggregateOn = !!aggregate && AGGREGATABLE.has(entityType);
  const baseEndpoint = `/api/v1/${entityType}/${entityId}/notes`;
  const listEndpoint = aggregateOn ? `${baseEndpoint}?aggregate=true` : baseEndpoint;
  const queryKey = [entityType, entityId, 'notes', aggregateOn ? 'aggregated' : 'own'];

  // Smooth animation when notes are added / edited / deleted — replaces
  // the abrupt re-render with a per-row fade/slide.
  const [animateRef] = useAutoAnimate<HTMLDivElement>();

  const { data: notes = [], isLoading } = useQuery<Note[]>({
    queryKey,
    queryFn: () => apiFetch<{ data: Note[] }>(listEndpoint).then((r) => r.data),
  });

  // Mutations always target the parent entity (client). Aggregated
  // notes from interests/yachts are read-only here — the rep edits
  // them on the source entity's page (we surface a "Open source" link
  // below). Keeping mutations against `baseEndpoint` keeps the POST
  // route handler clean.
  const createMutation = useMutation({
    mutationFn: (content: string) => apiFetch(baseEndpoint, { method: 'POST', body: { content } }),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey });
      setNewNote('');
    },
  });

  const updateMutation = useMutation({
    mutationFn: ({ noteId, content }: { noteId: string; content: string }) =>
      apiFetch(`${baseEndpoint}/${noteId}`, { method: 'PATCH', body: { content } }),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey });
      setEditingId(null);
    },
  });

  const deleteMutation = useMutation({
    mutationFn: (noteId: string) => apiFetch(`${baseEndpoint}/${noteId}`, { method: 'DELETE' }),
    onSuccess: () => queryClient.invalidateQueries({ queryKey }),
  });

  function canEdit(note: Note): boolean {
    if (note.authorId !== currentUserId) return false;
    if (note.isLocked) return false;
    // Aggregated view: only notes from THIS entity itself are editable
    // in-place. Notes pulled in from related entities (e.g. interests
    // surfaced under a client) must be edited on the source page so the
    // owning entity's timeline records the change.
    const selfSource = SELF_SOURCE[entityType];
    if (aggregateOn && note.source && note.source !== selfSource) return false;
    const elapsed = now - new Date(note.createdAt).getTime();
    return elapsed < NOTE_EDIT_WINDOW_MS;
  }

  function getTimeRemaining(note: Note): string | null {
    const elapsed = now - new Date(note.createdAt).getTime();
    const remaining = NOTE_EDIT_WINDOW_MS - elapsed;
    if (remaining <= 0) return null;
    const mins = Math.ceil(remaining / 60000);
    return `${mins}m left to edit`;
  }

  return (
    <div className="space-y-4">
      {/* Create note form */}
      <div className="space-y-2">
        <Textarea
          placeholder="Add a note..."
          value={newNote}
          onChange={(e) => setNewNote(e.target.value)}
          rows={3}
        />
        <div className="flex justify-end">
          <Button
            size="sm"
            disabled={!newNote.trim() || createMutation.isPending}
            onClick={() => createMutation.mutate(newNote.trim())}
          >
            {createMutation.isPending ? (
              <Loader2 className="mr-1.5 h-4 w-4 animate-spin" aria-hidden />
            ) : (
              <Send className="mr-1.5 h-4 w-4" aria-hidden />
            )}
            Add Note
          </Button>
        </div>
      </div>

      {/* Aggregated-mode controls — sort toggle. Only renders when
       *  aggregation is on and there's actually content to group. */}
      {aggregateOn && notes.length > 0 && (
        <div className="flex items-center justify-end gap-2 text-xs text-muted-foreground">
          <span>View:</span>
          <button
            type="button"
            className={!groupBySource ? 'font-medium text-foreground' : 'hover:text-foreground'}
            onClick={() => setGroupBySource(false)}
          >
            Chronological
          </button>
          <span>·</span>
          <button
            type="button"
            className={groupBySource ? 'font-medium text-foreground' : 'hover:text-foreground'}
            onClick={() => setGroupBySource(true)}
          >
            Group by source
          </button>
        </div>
      )}

      {/* Notes list */}
      {isLoading ? (
        <div className="text-center py-8 text-muted-foreground">Loading notes...</div>
      ) : notes.length === 0 ? (
        <div className="text-center py-8 text-muted-foreground">No notes yet</div>
      ) : (
        <div ref={animateRef} className="space-y-3">
          {(groupBySource ? sortByGroup(notes) : notes).map((note) => (
            <div key={note.id} className="flex gap-3 p-3 rounded-lg border bg-card">
              <Avatar className="h-8 w-8 shrink-0">
                <AvatarFallback className="text-xs">
                  {(note.authorName ?? 'U').charAt(0).toUpperCase()}
                </AvatarFallback>
              </Avatar>
              <div className="flex-1 min-w-0 space-y-1">
                <div className="flex items-center gap-2 text-sm flex-wrap">
                  <span className="font-medium">{note.authorName ?? 'User'}</span>
                  <span className="text-muted-foreground">
                    {formatDistanceToNow(new Date(note.createdAt), { addSuffix: true })}
                  </span>
                  {aggregateOn &&
                    note.source &&
                    note.source !== SELF_SOURCE[entityType] &&
                    note.sourceLabel && (
                      <span
                        className={`inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-medium ${SOURCE_BADGE_CLASS[note.source]}`}
                        title={`From ${note.source}`}
                      >
                        {SOURCE_LABEL[note.source]} · {note.sourceLabel}
                      </span>
                    )}
                  {/* Pipeline-stage stamp: shows what stage the linked
                      interest was at when the note was authored. Lets a
                      rep trace how the deal's notes evolved (concerns
                      raised at qualified vs after reservation). Only
                      populated for interest notes from 2026-05-15+. */}
                  {note.pipelineStageAtCreation && (
                    <span
                      className="inline-flex items-center rounded-full bg-indigo-50 px-1.5 py-0.5 text-[10px] font-medium text-indigo-900"
                      title="Pipeline stage when note was authored"
                    >
                      @ {stageLabel(note.pipelineStageAtCreation)}
                    </span>
                  )}
                  {note.isLocked && <Lock className="h-3 w-3 text-muted-foreground" aria-hidden />}
                  {canEdit(note) && (
                    <span className="text-xs text-muted-foreground">{getTimeRemaining(note)}</span>
                  )}
                </div>
                {editingId === note.id ? (
                  <div className="space-y-2">
                    <Textarea
                      value={editContent}
                      onChange={(e) => setEditContent(e.target.value)}
                      rows={3}
                    />
                    <div className="flex gap-2">
                      <Button
                        size="sm"
                        disabled={!editContent.trim() || updateMutation.isPending}
                        onClick={() =>
                          updateMutation.mutate({ noteId: note.id, content: editContent.trim() })
                        }
                      >
                        Save
                      </Button>
                      <Button size="sm" variant="ghost" onClick={() => setEditingId(null)}>
                        Cancel
                      </Button>
                    </div>
                  </div>
                ) : (
                  <p className="text-sm whitespace-pre-wrap">{note.content}</p>
                )}
              </div>
              {canEdit(note) && editingId !== note.id && (
                <div className="flex gap-1 shrink-0">
                  <Button
                    variant="ghost"
                    size="icon"
                    className="h-7 w-7"
                    onClick={() => {
                      setEditingId(note.id);
                      setEditContent(note.content);
                    }}
                  >
                    <Pencil className="h-3.5 w-3.5" aria-hidden />
                  </Button>
                  <Button
                    variant="ghost"
                    size="icon"
                    className="h-7 w-7 text-destructive"
                    onClick={() => deleteMutation.mutate(note.id)}
                  >
                    <Trash2 className="h-3.5 w-3.5" aria-hidden />
                  </Button>
                </div>
              )}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}
