import z from "zod"
import { NamedError } from "@saeeol/core/util/error"
import { BusEvent } from "@/bus/bus-event"
import { Effect, Schema, Context } from "effect"
import { zod as effectZod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"

export const Event = {
  Ready: BusEvent.define(
    "worktree.ready",
    Schema.Struct({
      name: Schema.String,
      branch: Schema.String,
    }),
  ),
  Failed: BusEvent.define(
    "worktree.failed",
    Schema.Struct({
      message: Schema.String,
    }),
  ),
}

export const Info = Schema.Struct({
  name: Schema.String,
  branch: Schema.String,
  directory: Schema.String,
})
  .annotate({ identifier: "Worktree" })
  .pipe(withStatics((s) => ({ zod: effectZod(s) })))
export type Info = Schema.Schema.Type<typeof Info>

export const CreateInput = Schema.Struct({
  name: Schema.optional(Schema.String),
  startCommand: Schema.optional(
    Schema.String.annotate({ description: "Additional startup script to run after the project's start command" }),
  ),
})
  .annotate({ identifier: "WorktreeCreateInput" })
  .pipe(withStatics((s) => ({ zod: effectZod(s) })))
export type CreateInput = Schema.Schema.Type<typeof CreateInput>

export const RemoveInput = Schema.Struct({
  directory: Schema.String,
})
  .annotate({ identifier: "WorktreeRemoveInput" })
  .pipe(withStatics((s) => ({ zod: effectZod(s) })))
export type RemoveInput = Schema.Schema.Type<typeof RemoveInput>

export const ResetInput = Schema.Struct({
  directory: Schema.String,
})
  .annotate({ identifier: "WorktreeResetInput" })
  .pipe(withStatics((s) => ({ zod: effectZod(s) })))
export type ResetInput = Schema.Schema.Type<typeof ResetInput>

export const NotGitError = NamedError.create(
  "WorktreeNotGitError",
  z.object({
    message: z.string(),
  }),
)

export const NameGenerationFailedError = NamedError.create(
  "WorktreeNameGenerationFailedError",
  z.object({
    message: z.string(),
  }),
)

export const CreateFailedError = NamedError.create(
  "WorktreeCreateFailedError",
  z.object({
    message: z.string(),
  }),
)

export const StartCommandFailedError = NamedError.create(
  "WorktreeStartCommandFailedError",
  z.object({
    message: z.string(),
  }),
)

export const RemoveFailedError = NamedError.create(
  "WorktreeRemoveFailedError",
  z.object({
    message: z.string(),
  }),
)

export const ResetFailedError = NamedError.create(
  "WorktreeResetFailedError",
  z.object({
    message: z.string(),
  }),
)

export function slugify(input: string) {
  return input
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+/, "")
    .replace(/-+$/, "")
}

export function failedRemoves(...chunks: string[]) {
  return chunks.filter(Boolean).flatMap((chunk) =>
    chunk
      .split("\n")
      .map((line) => line.trim())
      .flatMap((line) => {
        const match = line.match(/^warning:\s+failed to remove\s+(.+):\s+/i)
        if (!match) return []
        const value = match[1]?.trim().replace(/^['"]|['"]$/g, "")
        if (!value) return []
        return [value]
      }),
  )
}

export interface Interface {
  readonly makeWorktreeInfo: (name?: string) => Effect.Effect<Info>
  readonly createFromInfo: (info: Info, startCommand?: string) => Effect.Effect<void>
  readonly create: (input?: CreateInput) => Effect.Effect<Info>
  readonly remove: (input: RemoveInput) => Effect.Effect<boolean>
  readonly reset: (input: ResetInput) => Effect.Effect<boolean>
}

export class Service extends Context.Service<Service, Interface>()("@saeeol/Worktree") {}
