import { defineStore } from "pinia";
import { apiRequest } from "../data/api";
import { getUserDb } from "../data/db";
import { currentLedgerPreferenceId, ledgerAmountDisplayPreferenceId, ledgerSummaryRangePreferenceId, type LedgerRecord, type LedgerSummaryRange, type UserPreference } from "../data/ledgers";
import type { LedgerAmountDisplayMode } from "@cents/domain";
import { useAuthStore } from "./auth";

export type LedgerMember = {
  id: string;
  name: string;
  role: "owner" | "member";
  joinedAt: string;
};

let ledgerLoadPromise: Promise<void> | null = null;

export const useLedgerStore = defineStore("ledgers", {
  state: () => ({
    ledgers: [] as LedgerRecord[],
    currentLedgerId: "",
    currentMembers: [] as LedgerMember[],
    loaded: false,
  }),
  getters: {
    currentLedger(state) {
      return state.ledgers.find((ledger) => ledger.id === state.currentLedgerId) ?? null;
    },
    currentRole(): "owner" | "member" | null {
      return this.currentLedger?.role ?? null;
    },
    personalLedger(state) {
      return state.ledgers.find((ledger) => ledger.isPersonal) ?? null;
    },
  },
  actions: {
    async loadLedgers() {
      if (this.loaded) return;
      if (ledgerLoadPromise) return ledgerLoadPromise;
      ledgerLoadPromise = (async () => {
        const auth = useAuthStore();
        if (!auth.user) return;
        const db = await getUserDb(auth.user.id);
        let serverLedgers: LedgerRecord[];
        try {
          const result = await apiRequest<{ ledgers: LedgerRecord[] }>("/api/ledgers");
          serverLedgers = result.ledgers;
        } catch (error) {
          const cachedLedgers = await db.ledgers.toArray();
          if (!cachedLedgers.length) throw error;
          serverLedgers = cachedLedgers;
        }
        const displayPreferences = await db.userPreferences
          .where("userId")
          .equals(auth.user.id)
          .filter((preference) => preference.key === "ledgerAmountDisplay")
          .toArray();
        const summaryPreferences = await db.userPreferences
          .where("userId")
          .equals(auth.user.id)
          .filter((preference) => preference.key === "ledgerSummaryRange")
          .toArray();
        const displayByLedger = new Map(
          displayPreferences.map((preference) => [preference.id.split(":").at(-1), preference.value as LedgerAmountDisplayMode]),
        );
        const summaryByLedger = new Map(
          summaryPreferences.map((preference) => [preference.id.split(":").at(-1), preference.value as LedgerSummaryRange]),
        );
        const hydratedLedgers = serverLedgers.map((ledger) => ({
          ...ledger,
          amountDisplay: displayByLedger.get(ledger.id) ?? "base",
          summaryRange: summaryByLedger.get(ledger.id) ?? "year",
        }));
        this.ledgers = hydratedLedgers;

        await db.transaction("rw", db.ledgers, async () => {
          await db.ledgers.clear();
          if (hydratedLedgers.length) await db.ledgers.bulkPut(hydratedLedgers);
        });

        const preferenceId = currentLedgerPreferenceId(auth.user.id);
        const preference = await db.userPreferences.get(preferenceId);
        const preferredLedger = this.ledgers.find((ledger) => ledger.id === preference?.value);
        this.currentLedgerId = preferredLedger?.id ?? this.ledgers[0]?.id ?? "";
        if (this.currentLedgerId !== preference?.value) await this.persistCurrentLedger();
        this.loaded = true;
        try {
          await this.loadCurrentMembers();
        } catch {
          this.currentMembers = [];
        }
      })();
      try {
        await ledgerLoadPromise;
      } finally {
        ledgerLoadPromise = null;
      }
    },
    async reloadLedgers() {
      this.loaded = false;
      this.ledgers = [];
      this.currentLedgerId = "";
      this.currentMembers = [];
      await this.loadLedgers();
    },
    async setCurrentLedger(ledgerId: string) {
      const ledger = this.ledgers.find((item) => item.id === ledgerId && item.archivedAt === null);
      if (!ledger) throw new Error("Ledger not found");
      this.currentLedgerId = ledger.id;
      await this.persistCurrentLedger();
      await this.loadCurrentMembers();
    },
    async persistCurrentLedger() {
      const auth = useAuthStore();
      if (!this.currentLedgerId || !auth.user) return;
      const db = await getUserDb(auth.user.id);
      const preference: UserPreference = {
        id: currentLedgerPreferenceId(auth.user.id),
        userId: auth.user.id,
        key: "currentLedgerId",
        value: this.currentLedgerId,
        updatedAt: new Date().toISOString(),
      };
      await db.userPreferences.put(preference);
    },
    async loadCurrentMembers() {
      if (!this.currentLedgerId) {
        this.currentMembers = [];
        return;
      }
      try {
        const result = await apiRequest<{ members: LedgerMember[] }>(
          `/api/ledgers/${encodeURIComponent(this.currentLedgerId)}/members`,
        );
        this.currentMembers = result.members;
      } catch {
        this.currentMembers = [];
      }
    },
    memberName(userId: string) {
      return this.currentMembers.find((member) => member.id === userId)?.name ?? "历史用户";
    },
    async createLedger(input: Pick<LedgerRecord, "name" | "color" | "theme" | "icon" | "defaultCurrency">) {
      const result = await apiRequest<{ ledger: LedgerRecord }>("/api/ledgers", { method: "POST", body: input });
      await this.reloadLedgers();
      await this.setCurrentLedger(result.ledger.id);
      return result.ledger;
    },
    async updateLedger(input: Pick<LedgerRecord, "id" | "name" | "color" | "theme" | "icon" | "defaultCurrency"> & { amountDisplay?: LedgerAmountDisplayMode }) {
      const { amountDisplay, ...serverInput } = input;
      await apiRequest(`/api/ledgers/${encodeURIComponent(input.id)}`, { method: "PATCH", body: serverInput });
      if (amountDisplay) {
        const auth = useAuthStore();
        if (auth.user) {
          const db = await getUserDb(auth.user.id);
          const preference: UserPreference = {
            id: ledgerAmountDisplayPreferenceId(auth.user.id, input.id),
            userId: auth.user.id,
            key: "ledgerAmountDisplay",
            value: amountDisplay,
            updatedAt: new Date().toISOString(),
          };
          await db.userPreferences.put(preference);
        }
      }
      await this.reloadLedgers();
      return this.ledgers.find((ledger) => ledger.id === input.id)!;
    },
    async setSummaryRange(ledgerId: string, summaryRange: LedgerSummaryRange) {
      const auth = useAuthStore();
      if (!auth.user) return;
      const db = await getUserDb(auth.user.id);
      const preference: UserPreference = {
        id: ledgerSummaryRangePreferenceId(auth.user.id, ledgerId),
        userId: auth.user.id,
        key: "ledgerSummaryRange",
        value: summaryRange,
        updatedAt: new Date().toISOString(),
      };
      await db.userPreferences.put(preference);
      const ledger = this.ledgers.find((item) => item.id === ledgerId);
      if (ledger) ledger.summaryRange = summaryRange;
    },
  },
});
