import { publicProcedure, protectedProcedure, projectProcedure, TRPCError } from './router';
import { z } from 'zod';
import { eq, and, or, like, sql, inArray, asc } from 'drizzle-orm';
import type { DrizzleDB } from '../../src/db/config/migrations';
import {
  projects,
  characters,
  characterRelationships,
  scenes,
  sceneCharacters,
  projectMembers,
} from '../../src/db/schema';

function slugify(name: string): string {
  return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
}

async function getCharacterStatsImpl(
  db: DrizzleDB,
  characterId: number
) {
  const characterRow = await db.select()
    .from(characters)
    .where(eq(characters.id, characterId))
    .then(rows => rows[0]);

  if (!characterRow) return null;

  const sceneCharRows = await db.select()
    .from(sceneCharacters)
    .where(eq(sceneCharacters.characterId, characterId));

  const relRows = await db.select()
    .from(characterRelationships)
    .where(
      or(
        eq(characterRelationships.characterIdA, characterId),
        eq(characterRelationships.characterIdB, characterId)
      )
    );

  const sceneCount = sceneCharRows.length;
  const totalDialogueLines = sceneCharRows.reduce(
    (sum, sc) => sum + (sc.dialogueLines || 0), 0
  );
  const totalScreenTime = sceneCharRows.reduce(
    (sum, sc) => sum + (sc.screenTime || 0), 0
  );

  return {
    characterId,
    totalScreenTime,
    totalDialogueLines,
    sceneCount,
    relationshipCount: relRows.length,
  };
}

async function verifyProjectOwnership(
  db: DrizzleDB,
  projectId: number,
  userId: number
) {
  const projectRows = await db.select({ id: projects.id, ownerId: projects.ownerId })
    .from(projects)
    .where(eq(projects.id, projectId));

  const project = projectRows[0];
  if (!project) {
    throw new TRPCError({ code: 'NOT_FOUND', message: `Project ${projectId} not found` });
  }
  if (project.ownerId !== userId) {
    throw new TRPCError({ code: 'FORBIDDEN', message: `You do not have access to project ${projectId}` });
  }
  return project;
}

async function verifyProjectAccess(
  db: DrizzleDB,
  projectId: number,
  userId: number
) {
  const projectRows = await db.select({ id: projects.id, ownerId: projects.ownerId })
    .from(projects)
    .where(eq(projects.id, projectId));

  const project = projectRows[0];
  if (!project) {
    throw new TRPCError({ code: 'NOT_FOUND', message: `Project ${projectId} not found` });
  }
  if (project.ownerId === userId) return project;

  const memberRows = await db.select()
    .from(projectMembers)
    .where(and(eq(projectMembers.projectId, projectId), eq(projectMembers.userId, userId)));

  if (memberRows.length === 0) {
    throw new TRPCError({ code: 'FORBIDDEN', message: `You do not have access to project ${projectId}` });
  }
  return project;
}

async function verifyProjectRole(
  db: DrizzleDB,
  projectId: number,
  userId: number,
  allowedRoles: string[]
) {
  await verifyProjectAccess(db, projectId, userId);

  const projectRows = await db.select({ id: projects.id, ownerId: projects.ownerId })
    .from(projects)
    .where(eq(projects.id, projectId));
  const project = projectRows[0];
  if (!project) return;

  if (project.ownerId === userId) return;

  const memberRows = await db.select()
    .from(projectMembers)
    .where(and(eq(projectMembers.projectId, projectId), eq(projectMembers.userId, userId)));

  const member = memberRows[0];
  if (!member || !allowedRoles.includes(member.role)) {
    throw new TRPCError({ code: 'FORBIDDEN', message: 'Insufficient permissions' });
  }
}

export const projectRouter = {
  // Project procedures
  listProjects: protectedProcedure.query(async ({ ctx }) => {
    const owned = await ctx.db!.select()
      .from(projects)
      .where(eq(projects.ownerId, ctx.userId!))
      .orderBy(asc(projects.updatedAt));

    const memberRows = await ctx.db!.select({ projectId: projectMembers.projectId })
      .from(projectMembers)
      .where(eq(projectMembers.userId, ctx.userId!));

    const memberProjectIds = new Set(memberRows.map((r) => r.projectId));
    const memberProjects: typeof owned = [];

    for (const pid of memberProjectIds) {
      const row = await ctx.db!.select()
        .from(projects)
        .where(eq(projects.id, pid))
        .then((r) => r[0]);
      if (row) memberProjects.push(row);
    }

    const all = [...owned, ...memberProjects];
    const seen = new Set(all.map((p) => p.id));
    return all.filter((p) => seen.has(p.id));
  }),

  getProject: protectedProcedure
    .input(z.object({ id: z.number().int().positive() }))
    .query(async ({ input, ctx }) => {
      const rows = await ctx.db!.select()
        .from(projects)
        .where(eq(projects.id, input.id));
      const project = rows[0];
      if (!project) {
        throw new TRPCError({ code: 'NOT_FOUND', message: `Project ${input.id} not found` });
      }
      if (project.ownerId === ctx.userId || project.isPublic) return project;

      const memberRows = await ctx.db!.select()
        .from(projectMembers)
        .where(and(eq(projectMembers.projectId, input.id), eq(projectMembers.userId, ctx.userId!)));

      if (memberRows.length === 0) {
        throw new TRPCError({ code: 'FORBIDDEN', message: `You do not have access to project ${input.id}` });
      }
      return project;
    }),

  createProject: protectedProcedure
    .input(z.object({
      name: z.string().min(1).max(255),
      description: z.string().optional(),
    }))
    .mutation(async ({ input, ctx }) => {
      const result = await ctx.db!.insert(projects)
        .values({
          name: input.name,
          description: input.description ?? null,
          ownerId: ctx.userId!,
        })
        .returning();
      return result[0];
    }),

  updateProject: protectedProcedure
    .input(z.object({
      id: z.number().int().positive(),
      name: z.string().min(1).max(255).optional(),
      description: z.string().optional(),
    }))
    .mutation(async ({ input, ctx }) => {
      await verifyProjectOwnership(ctx.db!, input.id, ctx.userId!);

      const updateData: Record<string, any> = { updatedAt: new Date() };
      if (input.name !== undefined) updateData.name = input.name;
      if (input.description !== undefined) updateData.description = input.description ?? null;

      const result = await ctx.db!.update(projects)
        .set(updateData)
        .where(eq(projects.id, input.id))
        .returning();
      return result[0];
    }),

  deleteProject: protectedProcedure
    .input(z.object({ id: z.number().int().positive() }))
    .mutation(async ({ input, ctx }) => {
      await verifyProjectOwnership(ctx.db!, input.id, ctx.userId!);

      // Cascade delete: remove scenes first
      await ctx.db!.delete(scenes)
        .where(eq(scenes.projectId, input.id));

      // M2 fix: remove project members
      await ctx.db!.delete(projectMembers)
        .where(eq(projectMembers.projectId, input.id));

      // Get character IDs for this project
      const projectCharacters = await ctx.db!.select({ id: characters.id })
        .from(characters)
        .where(eq(characters.projectId, input.id));

      // Delete relationships for each character
      for (const char of projectCharacters) {
        await ctx.db!.delete(characterRelationships)
          .where(
            or(
              eq(characterRelationships.characterIdA, char.id),
              eq(characterRelationships.characterIdB, char.id)
            )
          );
      }

      // Delete characters
      await ctx.db!.delete(characters)
        .where(eq(characters.projectId, input.id));

      // Delete project
      const result = await ctx.db!.delete(projects)
        .where(eq(projects.id, input.id));

      return { success: true };
    }),

  // Character CRUD procedures
  listCharacters: protectedProcedure
    .input(z.object({ projectId: z.number().int().positive() }))
    .query(async ({ input, ctx }) => {
      await verifyProjectOwnership(ctx.db!, input.projectId, ctx.userId!);
      return await ctx.db!.select()
        .from(characters)
        .where(eq(characters.projectId, input.projectId))
        .orderBy(characters.name);
    }),

  getCharacter: protectedProcedure
    .input(z.object({ id: z.number().int().positive() }))
    .query(async ({ input, ctx }) => {
      const rows = await ctx.db!.select()
        .from(characters)
        .where(eq(characters.id, input.id));
      const character = rows[0];
      if (!character) {
        throw new TRPCError({ code: 'NOT_FOUND', message: `Character ${input.id} not found` });
      }
      await verifyProjectOwnership(ctx.db!, character.projectId, ctx.userId!);
      return character;
    }),

  createCharacter: protectedProcedure
    .input(z.object({
      name: z.string().min(1).max(100),
      description: z.string().optional(),
      bio: z.string().optional(),
      role: z.enum(['protagonist', 'antagonist', 'supporting', 'background', 'ensemble']).optional(),
      arc: z.string().optional(),
      arcType: z.enum(['positive', 'negative', 'flat', 'complex']).optional(),
      age: z.number().int().optional(),
      gender: z.string().optional(),
      voice: z.string().optional(),
      traits: z.string().optional(),
      motivation: z.string().optional(),
      conflict: z.string().optional(),
      secret: z.string().optional(),
      imageUrl: z.string().url().optional(),
      projectId: z.number().int().positive(),
    }))
    .mutation(async ({ input, ctx }) => {
      await verifyProjectOwnership(ctx.db!, input.projectId, ctx.userId!);

      const result = await ctx.db!.insert(characters)
        .values({
          name: input.name,
          slug: slugify(input.name),
          description: input.description ?? null,
          bio: input.bio ?? null,
          role: input.role ?? 'supporting',
          arc: input.arc ?? null,
          arcType: input.arcType ?? null,
          age: input.age ?? null,
          gender: input.gender ?? null,
          voice: input.voice ?? null,
          traits: input.traits ?? null,
          motivation: input.motivation ?? null,
          conflict: input.conflict ?? null,
          secret: input.secret ?? null,
          imageUrl: input.imageUrl ?? null,
          projectId: input.projectId,
        })
        .returning();
      return result[0];
    }),

  updateCharacter: protectedProcedure
    .input(z.object({
      id: z.number().int().positive(),
      name: z.string().min(1).max(100).optional(),
      description: z.string().optional(),
      bio: z.string().optional(),
      role: z.enum(['protagonist', 'antagonist', 'supporting', 'background', 'ensemble']).optional(),
      arc: z.string().optional(),
      arcType: z.enum(['positive', 'negative', 'flat', 'complex']).optional(),
      age: z.number().int().optional(),
      gender: z.string().optional(),
      voice: z.string().optional(),
      traits: z.string().optional(),
      motivation: z.string().optional(),
      conflict: z.string().optional(),
      secret: z.string().optional(),
      imageUrl: z.string().url().optional(),
      projectId: z.number().int().positive().optional(),
    }))
    .mutation(async ({ input, ctx }) => {
      const existingRows = await ctx.db!.select()
        .from(characters)
        .where(eq(characters.id, input.id));
      const existing = existingRows[0];
      if (!existing) {
        throw new TRPCError({ code: 'NOT_FOUND', message: `Character ${input.id} not found` });
      }
      await verifyProjectOwnership(ctx.db!, existing.projectId, ctx.userId!);

      const updateData: Record<string, any> = { updatedAt: new Date() };
      if (input.name !== undefined) {
        updateData.name = input.name;
        updateData.slug = slugify(input.name);
      }
      if (input.description !== undefined) updateData.description = input.description ?? null;
      if (input.bio !== undefined) updateData.bio = input.bio ?? null;
      if (input.role !== undefined) updateData.role = input.role;
      if (input.arc !== undefined) updateData.arc = input.arc ?? null;
      if (input.arcType !== undefined) updateData.arcType = input.arcType ?? null;
      if (input.age !== undefined) updateData.age = input.age ?? null;
      if (input.gender !== undefined) updateData.gender = input.gender ?? null;
      if (input.voice !== undefined) updateData.voice = input.voice ?? null;
      if (input.traits !== undefined) updateData.traits = input.traits ?? null;
      if (input.motivation !== undefined) updateData.motivation = input.motivation ?? null;
      if (input.conflict !== undefined) updateData.conflict = input.conflict ?? null;
      if (input.secret !== undefined) updateData.secret = input.secret ?? null;
      if (input.imageUrl !== undefined) updateData.imageUrl = input.imageUrl ?? null;
      if (input.projectId !== undefined) updateData.projectId = input.projectId;

      const result = await ctx.db!.update(characters)
        .set(updateData)
        .where(eq(characters.id, input.id))
        .returning();
      return result[0];
    }),

  deleteCharacter: protectedProcedure
    .input(z.object({ id: z.number().int().positive() }))
    .mutation(async ({ input, ctx }) => {
      const existingRows = await ctx.db!.select()
        .from(characters)
        .where(eq(characters.id, input.id));
      const existing = existingRows[0];
      if (!existing) {
        throw new TRPCError({ code: 'NOT_FOUND', message: `Character ${input.id} not found` });
      }
      await verifyProjectOwnership(ctx.db!, existing.projectId, ctx.userId!);

      // Remove associated relationships
      await ctx.db!.delete(characterRelationships)
        .where(
          or(
            eq(characterRelationships.characterIdA, input.id),
            eq(characterRelationships.characterIdB, input.id)
          )
        );

      await ctx.db!.delete(characters)
        .where(eq(characters.id, input.id));

      return { success: true };
    }),

  searchCharacters: protectedProcedure
    .input(z.object({
      projectId: z.number().int().positive(),
      query: z.string().optional(),
      role: z.enum(['protagonist', 'antagonist', 'supporting', 'background', 'ensemble']).optional(),
      arcType: z.enum(['positive', 'negative', 'flat', 'complex']).optional(),
    }))
    .query(async ({ input, ctx }) => {
      await verifyProjectOwnership(ctx.db!, input.projectId, ctx.userId!);

      const conditions: import('drizzle-orm').SQL[] = [eq(characters.projectId, input.projectId)];

      if (input.query) {
        const q = `%${input.query.toLowerCase()}%`;
        conditions.push(
          or(
            like(sql`LOWER(${characters.name})`, q),
            like(sql`LOWER(COALESCE(${characters.description}, ''))`, q),
            like(sql`LOWER(COALESCE(${characters.bio}, ''))`, q),
            like(sql`LOWER(COALESCE(${characters.traits}, ''))`, q),
            like(sql`LOWER(COALESCE(${characters.motivation}, ''))`, q)
          )!,
        );
      }

      if (input.role) {
        conditions.push(eq(characters.role, input.role));
      }

      if (input.arcType) {
        conditions.push(eq(characters.arcType, input.arcType));
      }

      return await ctx.db!.select()
        .from(characters)
        .where(and(...conditions))
        .orderBy(characters.name);
    }),

  getCharacterStats: protectedProcedure
    .input(z.object({ characterId: z.number().int().positive() }))
    .query(async ({ input, ctx }) => {
      const rows = await ctx.db!.select()
        .from(characters)
        .where(eq(characters.id, input.characterId));
      if (!rows[0]) {
        throw new TRPCError({ code: 'NOT_FOUND', message: `Character ${input.characterId} not found` });
      }
      await verifyProjectOwnership(ctx.db!, rows[0].projectId, ctx.userId!);
      return await getCharacterStatsImpl(ctx.db!, input.characterId);
    }),

  getProjectCharacterStats: projectProcedure
    .query(async ({ ctx }) => {
      const projectCharacters = await ctx.db!.select()
        .from(characters)
        .where(eq(characters.projectId, ctx.projectId!));
      const stats = [];
      for (const c of projectCharacters) {
        const s = await getCharacterStatsImpl(ctx.db!, c.id);
        if (s) stats.push(s);
      }
      return stats;
    }),

  // Relationship procedures
  listRelationships: projectProcedure
    .query(async ({ ctx }) => {
      const projectCharacterIds = await ctx.db!.select({ id: characters.id })
        .from(characters)
        .where(eq(characters.projectId, ctx.projectId!));
      const idList = projectCharacterIds.map(c => c.id);
      if (idList.length === 0) return [];

      return await ctx.db!.select()
        .from(characterRelationships)
        .where(
          and(
            inArray(characterRelationships.characterIdA, idList),
            inArray(characterRelationships.characterIdB, idList)
          )
        );
    }),

  getRelationshipsForCharacter: protectedProcedure
    .input(z.object({ characterId: z.number().int().positive() }))
    .query(async ({ input, ctx }) => {
      const rows = await ctx.db!.select()
        .from(characters)
        .where(eq(characters.id, input.characterId));
      if (!rows[0]) {
        throw new TRPCError({ code: 'NOT_FOUND', message: `Character ${input.characterId} not found` });
      }
      await verifyProjectOwnership(ctx.db!, rows[0].projectId, ctx.userId!);
      return await ctx.db!.select()
        .from(characterRelationships)
        .where(
          or(
            eq(characterRelationships.characterIdA, input.characterId),
            eq(characterRelationships.characterIdB, input.characterId)
          )
        );
    }),

  createRelationship: protectedProcedure
    .input(z.object({
      characterIdA: z.number().int().positive(),
      characterIdB: z.number().int().positive(),
      relationshipType: z.enum(['family', 'romantic', 'friendship', 'rivalry', 'mentor', 'alliance', 'conflict', 'professional', 'other']),
      description: z.string().optional(),
      strength: z.number().int().min(0).max(100).optional(),
      isAntagonistic: z.boolean().optional(),
    }))
    .mutation(async ({ input, ctx }) => {
      if (input.characterIdA === input.characterIdB) {
        throw new TRPCError({ code: 'BAD_REQUEST', message: 'Cannot create a relationship with the same character' });
      }

      const charARows = await ctx.db!.select()
        .from(characters)
        .where(eq(characters.id, input.characterIdA));
      const charBRows = await ctx.db!.select()
        .from(characters)
        .where(eq(characters.id, input.characterIdB));
      if (!charARows[0] || !charBRows[0]) {
        throw new TRPCError({ code: 'NOT_FOUND', message: 'Both characters must exist' });
      }
      await verifyProjectOwnership(ctx.db!, charARows[0].projectId, ctx.userId!);

      const existing = await ctx.db!.select()
        .from(characterRelationships)
        .where(
          or(
            and(
              eq(characterRelationships.characterIdA, input.characterIdA),
              eq(characterRelationships.characterIdB, input.characterIdB)
            ),
            and(
              eq(characterRelationships.characterIdA, input.characterIdB),
              eq(characterRelationships.characterIdB, input.characterIdA)
            )
          )
        );
      if (existing.length > 0) {
        throw new TRPCError({ code: 'CONFLICT', message: 'Relationship already exists between these characters' });
      }

      const result = await ctx.db!.insert(characterRelationships)
        .values({
          characterIdA: input.characterIdA,
          characterIdB: input.characterIdB,
          relationshipType: input.relationshipType,
          description: input.description ?? null,
          strength: input.strength ?? 50,
          isAntagonistic: input.isAntagonistic ?? false,
        })
        .returning();
      return result[0];
    }),

  updateRelationship: protectedProcedure
    .input(z.object({
      id: z.number().int().positive(),
      relationshipType: z.enum(['family', 'romantic', 'friendship', 'rivalry', 'mentor', 'alliance', 'conflict', 'professional', 'other']).optional(),
      description: z.string().optional(),
      strength: z.number().int().min(0).max(100).optional(),
      isAntagonistic: z.boolean().optional(),
    }))
    .mutation(async ({ input, ctx }) => {
      const relRows = await ctx.db!.select()
        .from(characterRelationships)
        .where(eq(characterRelationships.id, input.id));
      if (!relRows[0]) {
        throw new TRPCError({ code: 'NOT_FOUND', message: `Relationship ${input.id} not found` });
      }

      const charARows = await ctx.db!.select()
        .from(characters)
        .where(eq(characters.id, relRows[0].characterIdA));
      if (!charARows[0]) {
        throw new TRPCError({ code: 'NOT_FOUND', message: 'Character not found' });
      }
      await verifyProjectOwnership(ctx.db!, charARows[0].projectId, ctx.userId!);

      const updateData: Record<string, any> = { updatedAt: new Date() };
      if (input.relationshipType !== undefined) updateData.relationshipType = input.relationshipType;
      if (input.description !== undefined) updateData.description = input.description ?? null;
      if (input.strength !== undefined) updateData.strength = input.strength;
      if (input.isAntagonistic !== undefined) updateData.isAntagonistic = input.isAntagonistic;

      const result = await ctx.db!.update(characterRelationships)
        .set(updateData)
        .where(eq(characterRelationships.id, input.id))
        .returning();
      return result[0];
    }),

  deleteRelationship: protectedProcedure
    .input(z.object({ id: z.number().int().positive() }))
    .mutation(async ({ input, ctx }) => {
      const relRows = await ctx.db!.select()
        .from(characterRelationships)
        .where(eq(characterRelationships.id, input.id));
      if (!relRows[0]) {
        throw new TRPCError({ code: 'NOT_FOUND', message: `Relationship ${input.id} not found` });
      }

      const charARows = await ctx.db!.select()
        .from(characters)
        .where(eq(characters.id, relRows[0].characterIdA));
      if (!charARows[0]) {
        throw new TRPCError({ code: 'NOT_FOUND', message: 'Character not found' });
      }
      await verifyProjectOwnership(ctx.db!, charARows[0].projectId, ctx.userId!);

      await ctx.db!.delete(characterRelationships)
        .where(eq(characterRelationships.id, input.id));

      return { success: true };
    }),

  // Scene procedures
  listScenes: protectedProcedure
    .input(z.object({ projectId: z.number().int().positive() }))
    .query(async ({ input, ctx }) => {
      await verifyProjectOwnership(ctx.db!, input.projectId, ctx.userId!);
      return await ctx.db!.select()
        .from(scenes)
        .where(eq(scenes.projectId, input.projectId))
        .orderBy(scenes.order);
    }),

  getScene: protectedProcedure
    .input(z.object({ id: z.number().int().positive() }))
    .query(async ({ input, ctx }) => {
      const rows = await ctx.db!.select()
        .from(scenes)
        .where(eq(scenes.id, input.id));
      const scene = rows[0];
      if (!scene) {
        throw new TRPCError({ code: 'NOT_FOUND', message: `Scene ${input.id} not found` });
      }
      await verifyProjectOwnership(ctx.db!, scene.projectId, ctx.userId!);
      return scene;
    }),

  createScene: protectedProcedure
    .input(z.object({
      title: z.string().min(1),
      content: z.string().optional(),
      projectId: z.number().int().positive(),
      order: z.number().int().nonnegative(),
    }))
    .mutation(async ({ input, ctx }) => {
      await verifyProjectOwnership(ctx.db!, input.projectId, ctx.userId!);

      const result = await ctx.db!.insert(scenes)
        .values({
          title: input.title,
          content: input.content ?? '',
          projectId: input.projectId,
          order: input.order,
        })
        .returning();
      return result[0];
    }),

  updateScene: protectedProcedure
    .input(z.object({
      id: z.number().int().positive(),
      title: z.string().min(1).optional(),
      content: z.string().optional(),
      order: z.number().int().nonnegative().optional(),
    }))
    .mutation(async ({ input, ctx }) => {
      const rows = await ctx.db!.select()
        .from(scenes)
        .where(eq(scenes.id, input.id));
      if (!rows[0]) {
        throw new TRPCError({ code: 'NOT_FOUND', message: `Scene ${input.id} not found` });
      }

      const updateData: Record<string, any> = { updatedAt: new Date() };
      if (input.title !== undefined) updateData.title = input.title;
      if (input.content !== undefined) updateData.content = input.content ?? '';
      if (input.order !== undefined) updateData.order = input.order;

      const result = await ctx.db!.update(scenes)
        .set(updateData)
        .where(eq(scenes.id, input.id))
        .returning();
      return result[0];
    }),

   deleteScene: protectedProcedure
    .input(z.object({ id: z.number().int().positive() }))
    .mutation(async ({ input, ctx }) => {
      const rows = await ctx.db!.select()
        .from(scenes)
        .where(eq(scenes.id, input.id));
      if (!rows[0]) {
        throw new TRPCError({ code: 'NOT_FOUND', message: `Scene ${input.id} not found` });
      }

      // Check project ownership
      await verifyProjectOwnership(ctx.db!, rows[0].projectId, ctx.userId!);

      await ctx.db!.delete(scenes)
        .where(eq(scenes.id, input.id));

      return { success: true };
    }),

    // Project sharing and permissions
    listMembers: protectedProcedure
    .input(z.object({ projectId: z.number().int().positive() }))
    .query(async ({ input, ctx }) => {
      await verifyProjectAccess(ctx.db!, input.projectId, ctx.userId!);

      const members = await ctx.db!.select()
        .from(projectMembers)
        .where(eq(projectMembers.projectId, input.projectId))
        .orderBy(asc(projectMembers.addedAt));

      const projectRows = await ctx.db!.select()
        .from(projects)
        .where(eq(projects.id, input.projectId));
      const project = projectRows[0];
      if (!project) return members;

      return [
        { userId: project.ownerId, role: 'owner' as const, projectId: input.projectId, addedAt: project.createdAt, id: -1 },
        ...members,
      ];
    }),

    shareProject: protectedProcedure
    .input(z.object({
      projectId: z.number().int().positive(),
      userId: z.number().int().positive(),
      role: z.enum(['admin', 'editor', 'viewer']).default('editor'),
    }))
    .mutation(async ({ input, ctx }) => {
      await verifyProjectRole(ctx.db!, input.projectId, ctx.userId!, ['owner', 'admin']);

      if (input.userId === ctx.userId!) {
        throw new TRPCError({ code: 'BAD_REQUEST', message: 'You cannot share a project with yourself' });
      }

      const existing = await ctx.db!.select()
        .from(projectMembers)
        .where(and(eq(projectMembers.projectId, input.projectId), eq(projectMembers.userId, input.userId)));

      if (existing.length > 0) {
        throw new TRPCError({ code: 'CONFLICT', message: 'User is already a member of this project' });
      }

      const result = await ctx.db!.insert(projectMembers)
        .values({
          projectId: input.projectId,
          userId: input.userId,
          role: input.role,
        })
        .returning();
      return result[0];
    }),

    updateMemberRole: protectedProcedure
    .input(z.object({
      projectId: z.number().int().positive(),
      userId: z.number().int().positive(),
      role: z.enum(['admin', 'editor', 'viewer']),
    }))
    .mutation(async ({ input, ctx }) => {
      await verifyProjectRole(ctx.db!, input.projectId, ctx.userId!, ['owner']);

      const result = await ctx.db!.update(projectMembers)
        .set({ role: input.role })
        .where(and(eq(projectMembers.projectId, input.projectId), eq(projectMembers.userId, input.userId)))
        .returning();

      if (result.length === 0) {
        throw new TRPCError({ code: 'NOT_FOUND', message: 'Member not found' });
      }
      return result[0];
    }),

    removeMember: protectedProcedure
    .input(z.object({
      projectId: z.number().int().positive(),
      userId: z.number().int().positive(),
    }))
    .mutation(async ({ input, ctx }) => {
      await verifyProjectRole(ctx.db!, input.projectId, ctx.userId!, ['owner', 'admin']);

      if (input.userId === ctx.userId!) {
        throw new TRPCError({ code: 'FORBIDDEN', message: 'You cannot remove yourself from this project' });
      }

      await ctx.db!.delete(projectMembers)
        .where(and(eq(projectMembers.projectId, input.projectId), eq(projectMembers.userId, input.userId)));

      return { success: true };
    }),

    leaveProject: protectedProcedure
    .input(z.object({ projectId: z.number().int().positive() }))
    .mutation(async ({ input, ctx }) => {
      const projectRows = await ctx.db!.select()
        .from(projects)
        .where(eq(projects.id, input.projectId));
      const project = projectRows[0];
      if (!project) {
        throw new TRPCError({ code: 'NOT_FOUND', message: `Project ${input.projectId} not found` });
      }
      if (project.ownerId === ctx.userId!) {
        throw new TRPCError({ code: 'FORBIDDEN', message: 'Owner cannot leave the project. Transfer ownership first.' });
      }

      await ctx.db!.delete(projectMembers)
        .where(and(eq(projectMembers.projectId, input.projectId), eq(projectMembers.userId, ctx.userId!)));

      return { success: true };
    }),
};
