// adiuvAI/src/main/router/projectFolders.ts
import { TRPCError, initTRPC } from '@trpc/server';
import { z } from 'zod';
import { dialog } from 'electron';
import { eq } from 'drizzle-orm';
import { getDb } from '../db';
import { projects, projectFolderFiles } from '../db/schema';
import { startIndexSession, type IndexProgress } from '../files/indexer';
import { scanFolder } from '../files/scanner';
import { getBackendClient, QuotaError } from '../api/backend-client';
import type { TRPCContext } from '../ipc';

const t = initTRPC.context<TRPCContext>().create();
const router = t.router;
const publicProcedure = t.procedure;

// In-memory map of active sessions per projectId so we can cancel
const _active = new Map<string, { cancel: () => void; lastProgress: IndexProgress }>();

export const projectFoldersRouter = router({
  chooseFolder: publicProcedure.mutation(async () => {
    const result = await dialog.showOpenDialog({ properties: ['openDirectory'] });
    if (result.canceled || result.filePaths.length === 0) return null;
    return result.filePaths[0];
  }),

  link: publicProcedure
    .input(z.object({ projectId: z.string(), folderPath: z.string() }))
    .mutation(({ input }) => {
      const db = getDb();
      db.update(projects)
        .set({ folderPath: input.folderPath, folderLastScanStatus: 'idle', folderTotalFiles: 0 })
        .where(eq(projects.id, input.projectId))
        .run();
      return { ok: true };
    }),

  unlink: publicProcedure
    .input(z.object({ projectId: z.string() }))
    .mutation(({ input }) => {
      const db = getDb();
      db.delete(projectFolderFiles).where(eq(projectFolderFiles.projectId, input.projectId)).run();
      db.update(projects)
        .set({
          folderPath: null,
          folderLastScannedAt: null,
          folderLastScanStatus: 'idle',
          folderTotalFiles: 0,
        })
        .where(eq(projects.id, input.projectId))
        .run();
      return { ok: true };
    }),

  startScan: publicProcedure
    .input(z.object({ projectId: z.string() }))
    .mutation(async ({ input }) => {
      const db = getDb();
      const proj = db.select().from(projects).where(eq(projects.id, input.projectId)).get();
      if (!proj?.folderPath) throw new Error('No folder linked');
      if (proj.folderLastScanStatus === 'scanning') throw new Error('Scan already in progress');

      // Pre-flight: walk folder to estimate indexable file count, then ask the
      // backend whether the user's tier allows proceeding.
      const delta = await scanFolder(input.projectId, proj.folderPath);
      const estimated = delta.newFiles.length + delta.changedFiles.length + delta.unchangedCount;

      try {
        await getBackendClient().checkFolderQuota(estimated);
      } catch (err) {
        if (err instanceof QuotaError) {
          // Encode reason + backend message so the renderer can produce a
          // localised toast without an extra RPC call.
          throw new TRPCError({
            code: 'FORBIDDEN',
            message: `QUOTA:${err.reason}:${err.message}`,
          });
        }
        // Network / auth errors: propagate as-is so the renderer shows a
        // generic error toast rather than silently swallowing the problem.
        throw err;
      }

      const session = await startIndexSession(input.projectId, (p) => {
        const entry = _active.get(input.projectId);
        if (entry) entry.lastProgress = p;
        if (
          p.status === 'completed' ||
          p.status === 'cancelled' ||
          p.status === 'quota_exceeded' ||
          p.status === 'error'
        ) {
          _active.delete(input.projectId);
        }
      });
      _active.set(input.projectId, {
        cancel: session.cancel,
        lastProgress: { sessionId: session.sessionId, processed: 0, total: 0, status: 'starting' },
      });
      return { sessionId: session.sessionId };
    }),

  cancelScan: publicProcedure
    .input(z.object({ projectId: z.string() }))
    .mutation(({ input }) => {
      const entry = _active.get(input.projectId);
      if (entry) entry.cancel();
      return { ok: true };
    }),

  getStatus: publicProcedure
    .input(z.object({ projectId: z.string() }))
    .query(({ input }) => {
      const entry = _active.get(input.projectId);
      return entry?.lastProgress ?? null;
    }),

  listFiles: publicProcedure
    .input(z.object({ projectId: z.string() }))
    .query(({ input }) => {
      return getDb()
        .select()
        .from(projectFolderFiles)
        .where(eq(projectFolderFiles.projectId, input.projectId))
        .orderBy(projectFolderFiles.relativePath)
        .all();
    }),
});
