import { OutboxEventWriter } from "@domain/events"
import {
  type ConflictError,
  generateSlug,
  type ProjectId,
  type RepositoryError,
  SqlClient,
  toRepositoryError,
  type ValidationError,
} from "@domain/shared"
import { Effect } from "effect"
import { createProject } from "../entities/project.ts"
import { InvalidProjectNameError } from "../errors.ts"
import { ProjectRepository } from "../ports/project-repository.ts"

export interface CreateProjectInput {
  readonly id?: ProjectId
  readonly name: string
  readonly actorUserId?: string
}

export type CreateProjectError = RepositoryError | ValidationError | ConflictError | InvalidProjectNameError

export const createProjectUseCase = Effect.fn("projects.createProject")(function* (input: CreateProjectInput) {
  if (input.id) {
    yield* Effect.annotateCurrentSpan("project.id", input.id)
  }
  const trimmedName = input.name.trim()
  const sqlClient = yield* SqlClient
  const { organizationId } = sqlClient

  if (!trimmedName || trimmedName.length === 0) {
    return yield* new InvalidProjectNameError({
      field: input.name,
      message: "Name cannot be empty",
    })
  }

  if (trimmedName.length > 256) {
    return yield* new InvalidProjectNameError({
      field: input.name,
      message: "Name exceeds 256 characters",
    })
  }

  return yield* sqlClient.transaction(
    Effect.gen(function* () {
      const repo = yield* ProjectRepository
      const outboxEventWriter = yield* OutboxEventWriter

      const uniqueSlug = yield* generateSlug({
        name: trimmedName,
        count: (slug) => repo.countBySlug(slug),
      }).pipe(
        Effect.catchTag("InvalidSlugInputError", (error) =>
          Effect.fail(new InvalidProjectNameError({ field: input.name, message: error.reason })),
        ),
      )

      const project = createProject({
        id: input.id,
        organizationId,
        name: trimmedName,
        slug: uniqueSlug,
      })

      yield* repo.save(project)

      // Publish ProjectCreated event for downstream provisioning
      yield* outboxEventWriter
        .write({
          eventName: "ProjectCreated",
          aggregateType: "project",
          aggregateId: project.id,
          organizationId: project.organizationId,
          payload: {
            organizationId: project.organizationId,
            actorUserId: input.actorUserId ?? "",
            projectId: project.id,
            name: project.name,
            slug: project.slug,
          },
        })
        .pipe(Effect.mapError((error) => toRepositoryError(error, "write")))

      return project
    }),
  )
})
