"use client";

/**
 * IndexedDB-backed chat message storage.
 *
 * Dexie 实例可注入，便于测试用 fake-indexeddb 跑真 Dexie。
 *
 * 所有读取、替换和删除操作都必须携带 sessionId，避免同一浏览器中的游客、
 * 登录用户及多个账号共享缓存。
 */

import { Result, type Result as ResultT } from "@/utils/result";
import { LocalChatDB } from "./local_chat_db";
import { LocalMessage } from "./local_message";

export class LocalChatStorage {
  private readonly db: LocalChatDB;
  private static _instance: LocalChatStorage | null = null;

  constructor(db?: LocalChatDB) {
    this.db = db ?? new LocalChatDB();
  }

  static getInstance(): LocalChatStorage {
    if (!LocalChatStorage._instance) {
      LocalChatStorage._instance = new LocalChatStorage();
    }
    return LocalChatStorage._instance;
  }

  /** @internal */
  static _resetForTests(): void {
    LocalChatStorage._instance = null;
  }

  /**
   * 触发 Dexie 打开数据库。
   * 幂等：Dexie.open() 多次调用无副作用。
   * 在 bootstrap 中 await 此方法可保证后续操作不会因连接未开而延迟。
   */
  async init(): Promise<ResultT<void>> {
    try {
      await this.db.open();
      return Result.ok(undefined);
    } catch (e) {
      return Result.err(e);
    }
  }

  // ---- create / append ----

  async saveMessage(message: LocalMessage): Promise<ResultT<void>> {
    try {
      assertSessionId(message.sessionId);
      await this.db.messages.add(message.toRow());
      return Result.ok(undefined);
    } catch (e) {
      return Result.err(e);
    }
  }

  async replaceMessagesBySession(
    sessionId: string,
    messages: readonly LocalMessage[],
  ): Promise<ResultT<void>> {
    try {
      assertSessionId(sessionId);
      if (messages.some((message) => message.sessionId !== sessionId)) {
        throw new Error("Cannot write messages from a different chat identity.");
      }
      await this.db.transaction(
        "rw",
        this.db.messages,
        async () => {
          await this.db.messages.where("sessionId").equals(sessionId).delete();
          if (messages.length > 0) {
            await this.db.messages.bulkAdd(
              messages.map((message) => message.toRow()),
            );
          }
        },
      );
      return Result.ok(undefined);
    } catch (e) {
      return Result.err(e);
    }
  }

  // ---- read ----

  async getMessageCountBySession(sessionId: string): Promise<ResultT<number>> {
    try {
      assertSessionId(sessionId);
      return Result.ok(
        await this.db.messages.where("sessionId").equals(sessionId).count(),
      );
    } catch (e) {
      return Result.err(e);
    }
  }

  async getAllMessagesBySession(
    sessionId: string,
  ): Promise<ResultT<LocalMessage[]>> {
    try {
      assertSessionId(sessionId);
      const rows = await this.db.messages
        .where("sessionId")
        .equals(sessionId)
        .toArray();
      rows.sort((a, b) => (a.dbId ?? 0) - (b.dbId ?? 0));
      return Result.ok(rows.map((r) => LocalMessage.fromRow(r)));
    } catch (e) {
      return Result.err(e);
    }
  }

  // ---- delete ----

  async clearMessagesBySession(sessionId: string): Promise<ResultT<void>> {
    try {
      assertSessionId(sessionId);
      await this.db.messages.where("sessionId").equals(sessionId).delete();
      return Result.ok(undefined);
    } catch (e) {
      return Result.err(e);
    }
  }

  /**
   * 按数组下标删除（保持 Dart 端语义：`box.values.toList()[index].delete()`）。
   * O(n) 取下标后单条 delete，与 Dart 行为一致。
   */
  async deleteMessage(
    sessionId: string,
    index: number,
  ): Promise<ResultT<void>> {
    try {
      assertSessionId(sessionId);
      if (!Number.isInteger(index) || index < 0) {
        return Result.err(
          new RangeError(`deleteMessage: index ${index} out of range`),
        );
      }
      const rows = await this.db.messages
        .where("sessionId")
        .equals(sessionId)
        .toArray();
      if (index >= rows.length) {
        return Result.err(
          new RangeError(`deleteMessage: index ${index} out of range`),
        );
      }
      const target = rows[index]!;
      await this.db.messages.delete(target.dbId!);
      return Result.ok(undefined);
    } catch (e) {
      return Result.err(e);
    }
  }

  async close(): Promise<ResultT<void>> {
    try {
      this.db.close();
      return Result.ok(undefined);
    } catch (e) {
      return Result.err(e);
    }
  }
}

function assertSessionId(sessionId: string): void {
  if (sessionId.length === 0) {
    throw new Error("Chat cache identity must not be empty.");
  }
}
