import { BoardRepository } from '@/repositories/BoardRepository';
import { ChatRepository } from '@/repositories/ChatRepository';
import { TodoRepository } from '@/repositories/TodoRepository';
import { FileRepository } from '@/repositories/FileRepository';
import { CalendarRepository } from '@/repositories/CalendarRepository';
import { MeetingRepository } from '@/repositories/MeetingRepository';
import { MilestoneRepository } from '@/repositories/MilestoneRepository';
import { ProjectNoteRepository } from '@/repositories/ProjectNoteRepository';
import { ProjectMemberRepository } from '@/repositories/ProjectMemberRepository';
import { ForbiddenError } from '@/lib/errors';

export type SearchResourceType =
  | 'thread'
  | 'chat'
  | 'todo'
  | 'file'
  | 'event'
  | 'meeting'
  | 'milestone'
  | 'note';

export interface SearchResult {
  type: SearchResourceType;
  id: number;
  title: string;
  snippet: string;
}

export interface SearchOptions {
  q: string;
  type?: SearchResourceType;
}

// 検索は全件対象とする(ページネーション既定値による検索漏れを防ぐ)
const SEARCH_MAX = Number.MAX_SAFE_INTEGER;

/**
 * プロジェクト内の横断検索を担うService。
 * 各リソースを取得しキーワードで絞り込む(小規模データ前提)。
 */
export class SearchService {
  constructor(
    private readonly boardRepository: BoardRepository,
    private readonly chatRepository: ChatRepository,
    private readonly todoRepository: TodoRepository,
    private readonly fileRepository: FileRepository,
    private readonly calendarRepository: CalendarRepository,
    private readonly meetingRepository: MeetingRepository,
    private readonly milestoneRepository: MilestoneRepository,
    private readonly noteRepository: ProjectNoteRepository,
    private readonly projectMemberRepository: ProjectMemberRepository
  ) {}

  search(
    actorId: number,
    projectId: number,
    opts: SearchOptions
  ): SearchResult[] {
    this.requireMember(projectId, actorId);
    const q = opts.q.trim().toLowerCase();
    if (!q) return [];
    const type = opts.type;
    const results: SearchResult[] = [];
    const match = (text: string | null | undefined) =>
      !!text && text.toLowerCase().includes(q);

    if (!type || type === 'thread') {
      for (const t of this.boardRepository.findThreads(projectId, {
        page: 1,
        pageSize: SEARCH_MAX,
      }).items) {
        if (match(t.title) || match(t.bodyMd)) {
          results.push({
            type: 'thread',
            id: t.id,
            title: t.title,
            snippet: t.bodyMd.slice(0, 80),
          });
        }
      }
    }
    if (!type || type === 'chat') {
      for (const m of this.chatRepository.findMessages(projectId, {
        page: 1,
        pageSize: SEARCH_MAX,
      }).items) {
        if (match(m.body)) {
          results.push({
            type: 'chat',
            id: m.id,
            title: m.body.slice(0, 40),
            snippet: m.body.slice(0, 80),
          });
        }
      }
    }
    if (!type || type === 'todo') {
      for (const t of this.todoRepository.findItemsByProject(projectId)) {
        if (match(t.title) || match(t.description)) {
          results.push({
            type: 'todo',
            id: t.id,
            title: t.title,
            snippet: t.description?.slice(0, 80) ?? '',
          });
        }
      }
    }
    if (!type || type === 'file') {
      const files = this.fileRepository.findFilesByProject(
        projectId,
        1,
        SEARCH_MAX
      ).items;
      for (const f of files) {
        if (match(f.originalName)) {
          results.push({
            type: 'file',
            id: f.id,
            title: f.originalName,
            snippet: f.mimeType,
          });
        }
      }
    }
    if (!type || type === 'event') {
      for (const e of this.calendarRepository.findByProject(projectId)) {
        if (match(e.title) || match(e.description)) {
          results.push({
            type: 'event',
            id: e.id,
            title: e.title,
            snippet: e.description?.slice(0, 80) ?? '',
          });
        }
      }
    }
    if (!type || type === 'meeting') {
      for (const m of this.meetingRepository.findByProject(projectId)) {
        if (
          match(m.title) ||
          match(m.description) ||
          match(m.agendaMd) ||
          match(m.minutesMd)
        ) {
          results.push({
            type: 'meeting',
            id: m.id,
            title: m.title,
            snippet: m.description?.slice(0, 80) ?? '',
          });
        }
      }
    }
    if (!type || type === 'milestone') {
      for (const m of this.milestoneRepository.findByProject(projectId)) {
        if (match(m.title) || match(m.description)) {
          results.push({
            type: 'milestone',
            id: m.id,
            title: m.title,
            snippet: m.description?.slice(0, 80) ?? '',
          });
        }
      }
    }
    if (!type || type === 'note') {
      for (const n of this.noteRepository.findNotes(projectId, {
        page: 1,
        pageSize: SEARCH_MAX,
      }).items) {
        if (match(n.title) || match(n.bodyMd) || match(n.tags)) {
          results.push({
            type: 'note',
            id: n.id,
            title: n.title,
            snippet: n.bodyMd.slice(0, 80),
          });
        }
      }
    }
    return results;
  }

  private requireMember(projectId: number, actorId: number): void {
    if (!this.projectMemberRepository.isMember(projectId, actorId)) {
      throw new ForbiddenError('プロジェクトに参加していません');
    }
  }
}
