import { PermissionRuntime } from "../../permission/index.js";
import { ConcurrentToolScheduler, SequentialToolScheduler, ToolRuntime } from "../../tool/index.js";
import { AgentLoop, type AgentLoopSeedState } from "../loop/AgentLoop.js";
import type { AgentRuntimeConfig } from "../runtime/AgentRuntimeConfig.js";
import type { AgentRuntimeDependencies } from "../runtime/AgentRuntimeDependencies.js";
import { InMemoryTranscriptWriter } from "../../session/transcript/InMemoryTranscriptWriter.js";
import type { AgentTranscriptWriter } from "../../session/transcript/TranscriptWriter.js";
import { TurnRunner } from "../turn/TurnRunner.js";
import { AgentSession } from "./AgentSession.js";
import { createAgentEventBuffer, type AgentEvent } from "../protocol/events.js";
import type { AgentSessionState as AgentSessionStateShape } from "../protocol/state.js";
import {
  createAgentProjectSessionStorage,
  type AgentProjectSessionStorage,
  type AgentProjectSessionStorageOptions,
} from "../../session/storage/ProjectSessionStorage.js";

export type CreateAgentSessionOptions = {
  sessionId: string;
  config: AgentRuntimeConfig;
  dependencies: Omit<AgentRuntimeDependencies, "tools"> & {
    tools: Partial<AgentRuntimeDependencies["tools"]> & Pick<AgentRuntimeDependencies["tools"], "registry">;
  };
  transcript?: AgentTranscriptWriter;
  storage?: AgentProjectSessionStorage;
  projectStorage?: Omit<AgentProjectSessionStorageOptions, "sessionId" | "now">;
  initialState?: AgentSessionStateShape;
  seedState?: AgentLoopSeedState;
  replayEvents?: AgentEvent[];
};

export function createAgentSession(options: CreateAgentSessionOptions): AgentSession {
  return createAgentSessionWithStorage(options).session;
}

export function createAgentSessionWithStorage(options: CreateAgentSessionOptions): {
  session: AgentSession;
  storage?: AgentProjectSessionStorage;
} {
  const eventBuf = options.dependencies.drainEvents ? undefined : createAgentEventBuffer();
  const emitter = options.dependencies.eventEmitter ?? eventBuf?.emitter;
  const toolRuntime = new ToolRuntime(options.dependencies.tools.registry, new PermissionRuntime(), options.dependencies.lifecycle, emitter);
  const scheduler = options.dependencies.tools.scheduler
    ?? new ConcurrentToolScheduler(toolRuntime, options.dependencies.tools.registry);
  const dependencies: AgentRuntimeDependencies = {
    ...options.dependencies,
    tools: {
      registry: options.dependencies.tools.registry,
      scheduler,
    },
    eventEmitter: emitter,
    drainEvents: options.dependencies.drainEvents ?? eventBuf?.drain,
  };
  const loop = new AgentLoop(options.config, dependencies, options.seedState);
  const storage = options.storage ?? (
    options.projectStorage
      ? createAgentProjectSessionStorage({
          ...options.projectStorage,
          sessionId: options.sessionId,
          now: dependencies.now,
        })
      : undefined
  );
  const transcript = options.transcript ?? storage?.transcript ?? new InMemoryTranscriptWriter();
  const runtimeContext = {
    cwd: options.config.cwd,
    transcriptPath: storage?.transcriptPath ?? "",
  };
  const turnRunner = new TurnRunner(
    loop,
    transcript,
    undefined,
    dependencies.now,
    dependencies.lifecycle,
    runtimeContext,
  );
  return {
    session: new AgentSession({
      sessionId: options.sessionId,
      turnRunner,
      cwd: runtimeContext.cwd,
      transcriptPath: runtimeContext.transcriptPath,
      uuid: dependencies.uuid,
      initialState: options.initialState,
      replayEvents: options.replayEvents,
      lifecycle: dependencies.lifecycle,
    }),
    storage,
  };
}