import type {
  GhostNote,
  GraphEdge,
  GraphNode,
  KanbanColumn,
  MemoBlockCandidate,
  MemoSectionKind,
  NoteLink,
  NoteType,
  ResearchNote,
  ResearchWorkspace,
  TimelineEvent,
  WorkspaceProjection,
} from '../types/research';

const MEMO_ACCEPTED_THESIS_STATUSES = new Set([
  'accepted_support',
  'accepted_core',
  'bull_case',
  'bear_case',
] as const);

const BLOCKED_MEMO_NOTE_TYPES = new Set([
  'question',
  'follow_up_task',
  'source_reference',
] as const);

const TIMELINE_NOTE_TYPES = new Set([
  'event_takeaway',
  'catalyst',
  'management_signal',
] as const);

const GHOST_MEMO_STATES = new Set(['accepted', 'converted'] as const);

const noteSort = (left: ResearchNote, right: ResearchNote) => {
  if (left.pinned !== right.pinned) {
    return Number(right.pinned) - Number(left.pinned);
  }

  return right.updatedAt.localeCompare(left.updatedAt);
};

const ghostSort = (left: GhostNote, right: GhostNote) => {
  const leverageDelta = (right.leverageScore ?? 0) - (left.leverageScore ?? 0);
  if (leverageDelta !== 0) {
    return leverageDelta;
  }
  return right.updatedAt.localeCompare(left.updatedAt);
};

const sectionForNote = (noteType: NoteType): MemoSectionKind | null => {
  switch (noteType) {
    case 'thesis':
    case 'sub_thesis':
    case 'mosaic_insight':
      return 'investment_memo';
    case 'risk':
    case 'contradiction':
      return 'risk_register';
    case 'catalyst':
      return 'catalyst_calendar';
    case 'valuation_point':
    case 'scenario_assumption':
      return 'valuation_write_up';
    case 'event_takeaway':
      return 'earnings_recap';
    case 'channel_check':
      return 'watchlist_update';
    case 'fact':
    case 'quote':
    case 'management_signal':
    case 'claim':
    case 'industry_observation':
    case 'competitor_comparison':
      return 'stock_pitch';
    case 'question':
    case 'follow_up_task':
    case 'source_reference':
    case 'uncertainty':
      return null;
  }
};

const buildMemoBlocks = (
  notes: ResearchNote[],
  ghosts: GhostNote[],
): MemoBlockCandidate[] => {
  const noteBlocks = notes.flatMap((note) => {
    if (
      note.archived ||
      BLOCKED_MEMO_NOTE_TYPES.has(note.noteType as never) ||
      note.evidenceStatus === 'unsourced'
    ) {
      return [];
    }

    const sectionKind = sectionForNote(note.noteType);
    if (!sectionKind) {
      return [];
    }

    return [
      {
        sectionKind,
        headline: note.title || note.cleanedText,
        body: note.aiAnnotation || note.cleanedText,
        sourceNoteIds: [note.id],
        citationRefs: note.sourceId ? [note.sourceId] : [],
        confidence: note.confidence,
        accepted: MEMO_ACCEPTED_THESIS_STATUSES.has(note.thesisStatus as never),
      },
    ];
  });

  const ghostBlocks = ghosts.flatMap((ghost) => {
    if (!GHOST_MEMO_STATES.has(ghost.state as never) || !ghost.memoSectionHint) {
      return [];
    }

    return [
      {
        sectionKind: ghost.memoSectionHint,
        headline: ghost.headline,
        body: ghost.body,
        sourceNoteIds: ghost.supportingNoteIds,
        citationRefs: ghost.sourceIds,
        confidence: ghost.confidence,
        accepted: true,
      },
    ];
  });

  return [...noteBlocks, ...ghostBlocks];
};

const buildGraphNodes = (notes: ResearchNote[]): GraphNode[] =>
  notes.map((note) => ({
    id: note.id,
    label: note.title || note.cleanedText,
    kind: note.noteType,
    confidence: note.confidence,
    evidenceStatus: note.evidenceStatus,
  }));

const buildGraphEdges = (links: NoteLink[]): GraphEdge[] =>
  links.map((link) => ({
    id: link.id,
    from: link.fromNoteId,
    to: link.toNoteId,
    linkType: link.linkType,
    strength: link.strength,
    confidence: link.confidence,
  }));

const buildKanbanColumns = (notes: ResearchNote[]): KanbanColumn[] => {
  const noteTypes: NoteType[] = [
    'fact',
    'management_signal',
    'claim',
    'risk',
    'catalyst',
    'valuation_point',
    'question',
    'source_reference',
  ];

  return noteTypes.map((noteType) => ({
    key: noteType,
      label:
        noteType === 'valuation_point'
          ? 'Valuation'
          : noteType === 'management_signal'
            ? 'Management Signal'
            : noteType.replace(/_/g, ' '),
    notes: notes
      .filter((note) => note.noteType === noteType && !note.archived)
      .sort(noteSort),
  }));
};

const buildTimeline = (
  notes: ResearchNote[],
  ghosts: GhostNote[],
): TimelineEvent[] => {
  const noteEvents = notes.flatMap((note) => {
    if (!TIMELINE_NOTE_TYPES.has(note.noteType as never)) {
      return [];
    }

    return [
      {
        id: note.id,
        label: note.title || note.cleanedText,
        noteId: note.id,
        at: note.sourceExcerpt?.locationLabel,
      },
    ];
  });

  const ghostEvents = ghosts.flatMap((ghost) => {
    if (ghost.ghostClass !== 'contradiction_alert') {
      return [];
    }

    return [
      {
        id: ghost.id,
        label: ghost.headline,
        noteId: ghost.supportingNoteIds[0] ?? ghost.id,
      },
    ];
  });

  return [...noteEvents, ...ghostEvents];
};

const sortNotes = (notes: ResearchNote[]) => [...notes].sort(noteSort);

const sortGhosts = (ghosts: GhostNote[]) => [...ghosts].sort(ghostSort);

export const rebuildWorkspaceProjection = (
  projection: WorkspaceProjection,
  overrides: Partial<Pick<WorkspaceProjection, 'workspace' | 'notes' | 'links' | 'ghosts'>>,
): WorkspaceProjection => {
  const workspace = overrides.workspace ?? projection.workspace;
  const notes = sortNotes(overrides.notes ?? projection.notes);
  const links = overrides.links ?? projection.links;
  const ghosts = sortGhosts(overrides.ghosts ?? projection.ghosts);

  return {
    workspace,
    activeView: projection.activeView,
    notes,
    links,
    ghosts,
    memoBlocks: buildMemoBlocks(notes, ghosts),
    graphNodes: buildGraphNodes(notes),
    graphEdges: buildGraphEdges(links),
    kanbanColumns: buildKanbanColumns(notes),
    timelineEvents: buildTimeline(notes, ghosts),
  };
};

export const upsertProjectionNote = (
  projection: WorkspaceProjection,
  note: ResearchNote,
): WorkspaceProjection => {
  const notes = projection.notes.some((current) => current.id === note.id)
    ? projection.notes.map((current) => (current.id === note.id ? note : current))
    : [note, ...projection.notes];

  return rebuildWorkspaceProjection(projection, { notes });
};

export const upsertProjectionGhost = (
  projection: WorkspaceProjection,
  ghost: GhostNote,
): WorkspaceProjection => {
  const ghosts = projection.ghosts.some((current) => current.id === ghost.id)
    ? projection.ghosts.map((current) => (current.id === ghost.id ? ghost : current))
    : [ghost, ...projection.ghosts];

  return rebuildWorkspaceProjection(projection, { ghosts });
};

export const replaceProjectionWorkspace = (
  projection: WorkspaceProjection,
  workspace: ResearchWorkspace,
): WorkspaceProjection => rebuildWorkspaceProjection(projection, { workspace });
