import { Effect, Context, Layer } from "effect";
import { EventBus } from "@reactive-agents/core";
import type {
  MemoryBootstrapResult,
  MemoryConfig,
  MemoryLLM,
  SemanticEntry,
  DailyLogEntry,
  WorkingMemoryItem,
  MemoryId,
  SessionSnapshot,
} from "../types.js";
import { MemoryError, DatabaseError } from "../errors.js";
import { WorkingMemoryService } from "./working-memory.js";
import { SemanticMemoryService } from "./semantic-memory.js";
import { EpisodicMemoryService } from "./episodic-memory.js";
import { ProceduralMemoryService } from "./procedural-memory.js";
import { MemoryFileSystem } from "../fs/memory-file-system.js";
import { ZettelkastenService } from "../indexing/zettelkasten.js";
import { emitErrorSwallowed, errorTag } from "@reactive-agents/core";

// ─── Service Tag ───

export class MemoryService extends Context.Tag("MemoryService")<
  MemoryService,
  {
    /**
     * Bootstrap: load semantic context + recent episodes for agent.
     * Called by ExecutionEngine at Phase 1 (BOOTSTRAP).
     */
    readonly bootstrap: (
      agentId: string,
    ) => Effect.Effect<MemoryBootstrapResult, MemoryError | DatabaseError>;

    /**
     * Flush: generate memory.md projection from SQLite and write to disk.
     */
    readonly flush: (
      agentId: string,
    ) => Effect.Effect<void, MemoryError | DatabaseError>;

    /**
     * Snapshot: save session messages to episodic SQLite storage.
     */
    readonly snapshot: (
      snapshot: SessionSnapshot,
    ) => Effect.Effect<void, DatabaseError>;

    /**
     * Store a working memory item (adds to in-process Ref).
     */
    readonly addToWorking: (
      item: WorkingMemoryItem,
    ) => Effect.Effect<void, never>;

    /**
     * Store a semantic memory entry (persists to SQLite).
     * Auto-links via Zettelkasten if enabled.
     */
    readonly storeSemantic: (
      entry: SemanticEntry,
    ) => Effect.Effect<MemoryId, DatabaseError>;

    /**
     * Log an episodic event (persists to SQLite).
     */
    readonly logEpisode: (
      entry: DailyLogEntry,
    ) => Effect.Effect<MemoryId, DatabaseError>;

    /**
     * Get current working memory contents.
     */
    readonly getWorking: () => Effect.Effect<
      readonly WorkingMemoryItem[],
      never
    >;
  }
>() {}

// ─── Live Implementation ───

export const MemoryServiceLive = (config: MemoryConfig, memoryLLM?: MemoryLLM) =>
  Layer.effect(
    MemoryService,
    Effect.gen(function* () {
      const working = yield* WorkingMemoryService;
      const semantic = yield* SemanticMemoryService;
      const episodic = yield* EpisodicMemoryService;
      const _procedural = yield* ProceduralMemoryService;
      const fileSystem = yield* MemoryFileSystem;
      const zettel = yield* ZettelkastenService;

      // EventBus is optional — publish memory lifecycle events when available
      const ebOpt = yield* Effect.serviceOption(EventBus).pipe(
        Effect.catchAll(() => Effect.succeed({ _tag: "None" as const })),
      );
      const eb = ebOpt._tag === "Some" ? ebOpt.value : null;

      const basePath = `.reactive-agents/memory`;

      return {
        bootstrap: (agentId) =>
          Effect.gen(function* () {
            // Ensure directory exists
            yield* fileSystem
              .ensureDirectory(agentId, basePath)
              .pipe(Effect.catchAll((err) => emitErrorSwallowed({ site: "memory/src/services/memory-service.ts:107", tag: errorTag(err) })));

            // Generate semantic context from live SQLite (not stale memory.md)
            const semanticContext = yield* semantic
              .generateMarkdown(agentId, config.semantic.maxMarkdownLines)
              .pipe(Effect.catchAll((err) => emitErrorSwallowed({ site: "memory/src/services/memory-service.ts:112", tag: errorTag(err) }).pipe(Effect.as(""))));

            // Get recent episodic entries (last 20)
            const recentEpisodes = yield* episodic
              .getRecent(agentId, 20)
              .pipe(Effect.catchAll((err) => emitErrorSwallowed({ site: "memory/src/services/memory-service.ts:117", tag: errorTag(err) }).pipe(Effect.as([] as DailyLogEntry[]))));

            // Get active workflows
            const activeWorkflows = yield* _procedural
              .listActive(agentId)
              .pipe(Effect.catchAll((err) => emitErrorSwallowed({ site: "memory/src/services/memory-service.ts:122", tag: errorTag(err) }).pipe(Effect.as([] as never[]))));

            // Get current working memory
            const workingMemory = yield* working.get();

            const result = {
              agentId,
              semanticContext,
              recentEpisodes,
              activeWorkflows,
              workingMemory: [...workingMemory],
              activeSkills: [] as unknown[],
              bootstrappedAt: new Date(),
              tier: config.tier,
            } satisfies MemoryBootstrapResult;

            if (eb) {
              yield* eb.publish({ _tag: "MemoryBootstrapped", agentId, tier: config.tier })
                .pipe(Effect.catchAll((err) => emitErrorSwallowed({ site: "memory/src/services/memory-service.ts:140", tag: errorTag(err) })));
            }

            return result;
          }),

        flush: (agentId) =>
          Effect.gen(function* () {
            const markdown = yield* semantic.generateMarkdown(
              agentId,
              config.semantic.maxMarkdownLines,
            );
            yield* fileSystem.writeMarkdown(agentId, markdown, basePath);
            if (eb) {
              yield* eb.publish({ _tag: "MemoryFlushed", agentId })
                .pipe(Effect.catchAll((err) => emitErrorSwallowed({ site: "memory/src/services/memory-service.ts:155", tag: errorTag(err) })));
            }
          }),

        snapshot: (snap) => episodic.saveSnapshot(snap),

        addToWorking: (item) => working.add(item),

        storeSemantic: (entry) =>
          Effect.gen(function* () {
            // Auto-generate embedding if MemoryLLM.embed is available and entry has none
            let entryToStore = entry;
            if (
              memoryLLM?.embed &&
              (!entry.embedding || entry.embedding.length === 0)
            ) {
              const embeddingResult = yield* memoryLLM
                .embed([entry.content])
                .pipe(
                  Effect.map((embeddings) =>
                    embeddings.length > 0 ? [...embeddings[0]!] : undefined,
                  ),
                  Effect.catchAll(() => Effect.succeed(undefined as number[] | undefined)),
                );
              if (embeddingResult && embeddingResult.length > 0) {
                entryToStore = { ...entry, embedding: embeddingResult };
              }
            }
            const id = yield* semantic.store(entryToStore);
            // Auto-link if Zettelkasten enabled
            if (config.zettelkasten.enabled) {
              yield* zettel
                .autoLinkText(
                  entry.id,
                  entry.content,
                  entry.agentId,
                  config.zettelkasten.linkingThreshold,
                )
                .pipe(Effect.catchAll((err) => emitErrorSwallowed({ site: "memory/src/services/memory-service.ts:188", tag: errorTag(err) }).pipe(Effect.as([]))));
            }
            return id;
          }),

        logEpisode: (entry) => episodic.log(entry),

        getWorking: () => working.get(),
      };
    }),
  );
